<commit_before>package main

import (
	"bufio"
	"encoding/csv"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"os"
	"strings"
	"sync"
	"time"

	"github.com/fsnotify/fsnotify"
	"github.com/gorilla/mux"
	"github.com/spf13/viper"
)

type Program struct {
	URL      string
	Duration time.Duration
}

// InitializeConfig loads our configuration using Viper package.
func InitializeConfig() {

	viper.SetConfigType("yaml")
	viper.SetConfigName("config")

	viper.AddConfigPath("$HOME/.gotator")
	viper.AddConfigPath(".")

	err := viper.ReadInConfig()
	if err != nil {
		panic(fmt.Errorf("Fatal error config file: %s \n", err))
	}

	viper.SetDefault("debug", false)

	viper.SetEnvPrefix("gorotator") // will be uppercased automatically
	viper.BindEnv("debug")
	viper.BindEnv("firefox_ip")
	viper.BindEnv("firefox_port")
	viper.BindEnv("gotator_port")

	if !viper.IsSet("firefox_ip") || !viper.IsSet("firefox_port") {
		fmt.Fprintln(os.Stderr, "Configuration error.  Both FIREFOX_IP and FIREFOX_PORT must be set via either config or environment.")
		os.Exit(1)
	}

	viper.WatchConfig()
	viper.OnConfigChange(func(e fsnotify.Event) {
		fmt.Println("\nConfig file changed:", e.Name)
		skip <- struct{}{}
		fmt.Printf("Content will change immediately.\n\n")
	})

}

// Loads a list of programs.
// A program consists of a list things to display on the rotator along
// with a number of seconds to display each one before moving on.
func loadProgramList(filename string) []Program {

	fmt.Printf("Loading programs from %s\n", filename)

	var list []Program

	bytes, err := ioutil.ReadFile(filename)
	if err != nil {
		log.Fatal(err)
	}
	webpages := string(bytes)

	r := csv.NewReader(strings.NewReader(webpages))
	r.LazyQuotes = true

	for {
		var p Program
		record, err := r.Read()
		if err == io.EOF {
			fmt.Printf("Finished loading programs.\n\n")
			break
		}
		if err != nil {
			log.Printf("Error reading line from program file: %s.  Abandoning attempt to read programs.\n", filename)
			log.Fatal(err)
		}
		p.URL = record[0]
		p.Duration, err = time.ParseDuration(record[1])
		if err != nil {
			fmt.Println("Program rejected.  Invalid duration.")
		}

		fmt.Printf("  Loaded program %.50s to show for %s.\n", p.URL, p.Duration)
		list = append(list, p)
	}

	return list
}

func runProgram(program Program) {

	ip := viper.Get("FIREFOX_IP")
	port := viper.GetInt("FIREFOX_PORT")

	constr := fmt.Sprintf("%s:%d", ip, port)

	conn, err := net.Dial("tcp", constr)
	if err != nil {
		fmt.Printf("Error making network connection to: %s\n", constr)
		fmt.Println("It is possible Firefox needs to be started or restarted.")
		fmt.Println("Pausing for 30s")
		time.Sleep(30 * time.Second) // wait 30 seconds to slow retries
		return
	}

	fmt.Printf("Running program for %s:  %s -> RESULT: ", program.Duration, program.URL)
	fmt.Fprintf(conn, "window.location='%s'\n", program.URL)
	status, err := bufio.NewReader(conn).ReadString('\n')
	if err != nil {
		fmt.Println("ERROR - URL didn't load as desired.")
	}

	var statusParsed interface{}
	err = json.Unmarshal([]byte(status), &statusParsed)

	m := statusParsed.(map[string]interface{})

	if m["result"] == program.URL {
		fmt.Println("OK")
	} else {
		fmt.Println("ERROR - URL didn't load as desired.")
	}

	// fmt.Printf("  %s", status)

	select {
	case <-time.After(program.Duration):
		// Do nothing.
		Unpause()
	case <-skip:
		fmt.Println("Current program skipped")
		return
	}
}

func Pause() {
	mu.Lock()
	pause = true
	mu.Unlock()
}

func Unpause() {
	mu.Lock()
	pause = false
	mu.Unlock()
}

func IsPaused() bool {
	mu.Lock()
	defer mu.Unlock()

	return pause == true
}

func LoadAndRunLoop() {

	// Load and run the acctive program_file indefinately
	for {
		// We pull filename inside the loop because the
		// configuration can change while our program is running.
		filename := viper.GetString("program_file")
		for IsPaused() {
			fmt.Println("Paused.")
			time.Sleep(1 * time.Second)
		}

		pl := loadProgramList(filename)

		for _, p := range pl {
			for IsPaused() {
				fmt.Println("Program list is paused.")
				time.Sleep(1 * time.Second)
			}
			runProgram(p)
		}

		fmt.Printf("\nLooping back to play program list from beginning\n\n")
	}

}

func PlayHandler(w http.ResponseWriter, r *http.Request) {

	r.ParseForm()
	var p Program
	p.URL = r.Form.Get("url")
	fmt.Printf("URL: %s\n", p.URL)

	d := r.Form.Get("duration")
	fmt.Printf("Duration: %s\n", d)

	var err error
	p.Duration, err = time.ParseDuration(r.Form.Get("duration"))
	if err != nil {
		w.Write([]byte("Program rejected.  Invalid duration.\n"))
		return
	}

	// Needs validation...

	// Now do something with the program.. play it?

	// Stop normal rotation
	Pause()
	skip <- struct{}{}

	go runProgram(p)
	w.Write([]byte("Program accepted\n"))

}

func PauseHandler(w http.ResponseWriter, r *http.Request) {
	Pause()
	log.Println("Pausing from web request")
	w.Write([]byte("Ok, paused.\n"))
}

func ResumeHandler(w http.ResponseWriter, r *http.Request) {
	Unpause()
	log.Println("Unpausing from web request")
	w.Write([]byte("Ok, unpaused.\n"))
}
func SkipHandler(w http.ResponseWriter, r *http.Request) {
	log.Println("Skippingfrom web request")
	Unpause()
	skip <- struct{}{}

	w.Write([]byte("Skipping current programming and resume program list runner from web request.\n"))
}

func readKeyboardLoop() {
	for {
		os.Stdin.Read(make([]byte, 1)) // read a single byte
		fmt.Printf(" >> Got keyboard input, that means you want to move to the next program.  Can do! << \n\n")
		Unpause()
		skip <- struct{}{}
	}
}

// Control channel to stop running programs immediately (yes, global)

var skip = make(chan struct{})
var exitprogram = make(chan struct{})
var pause bool
var mu = &sync.Mutex{}
var version = "0.0.4"

func main() {

	if len(os.Args) > 1 {
		if os.Args[1] == "version" {
			fmt.Println("Gotator version:", version)
			os.Exit(0)
		}
	}
	fmt.Println("Starting gotator: version", version)
	Unpause()

	InitializeConfig()

	go LoadAndRunLoop()
	go readKeyboardLoop()

	if viper.IsSet("apienabled") && viper.Get("apienabled") == true {
		listen_port := ":8080"
		if viper.IsSet("gotator_port") {
			listen_port = ":" + viper.GetString("gotator_port")
		}

		fmt.Printf("Starting API server on port %s.  Notice:  This allows UNAUTHENTICATED remote control of Firefox. set 'apienabled: false' in config.yaml to disable.\n",
			listen_port)

		r := mux.NewRouter()
		r.HandleFunc("/play", PlayHandler)
		r.HandleFunc("/pause", PauseHandler)
		r.HandleFunc("/resume", ResumeHandler)
		r.HandleFunc("/skip", SkipHandler)

		go log.Fatal(http.ListenAndServe(listen_port, r))
	} else {
		fmt.Println("notice: rest API not enabled in configuration and will be unavailable.  set 'apienabled: true' in config.yaml if you want to use it.\n")
		// If we aren't doing http.ListenAndServe() we need to block here or else gotator would exit immediately
		<-exitprogram
	}

}
<commit_msg>Removed commented code.<commit_after>package main

import (
	"bufio"
	"encoding/csv"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"os"
	"strings"
	"sync"
	"time"

	"github.com/fsnotify/fsnotify"
	"github.com/gorilla/mux"
	"github.com/spf13/viper"
)

type Program struct {
	URL      string
	Duration time.Duration
}

// InitializeConfig loads our configuration using Viper package.
func InitializeConfig() {

	viper.SetConfigType("yaml")
	viper.SetConfigName("config")

	viper.AddConfigPath("$HOME/.gotator")
	viper.AddConfigPath(".")

	err := viper.ReadInConfig()
	if err != nil {
		panic(fmt.Errorf("Fatal error config file: %s \n", err))
	}

	viper.SetDefault("debug", false)

	viper.SetEnvPrefix("gorotator") // will be uppercased automatically
	viper.BindEnv("debug")
	viper.BindEnv("firefox_ip")
	viper.BindEnv("firefox_port")
	viper.BindEnv("gotator_port")

	if !viper.IsSet("firefox_ip") || !viper.IsSet("firefox_port") {
		fmt.Fprintln(os.Stderr, "Configuration error.  Both FIREFOX_IP and FIREFOX_PORT must be set via either config or environment.")
		os.Exit(1)
	}

	viper.WatchConfig()
	viper.OnConfigChange(func(e fsnotify.Event) {
		fmt.Println("\nConfig file changed:", e.Name)
		skip <- struct{}{}
		fmt.Printf("Content will change immediately.\n\n")
	})

}

// Loads a list of programs.
// A program consists of a list things to display on the rotator along
// with a number of seconds to display each one before moving on.
func loadProgramList(filename string) []Program {

	fmt.Printf("Loading programs from %s\n", filename)

	var list []Program

	bytes, err := ioutil.ReadFile(filename)
	if err != nil {
		log.Fatal(err)
	}
	webpages := string(bytes)

	r := csv.NewReader(strings.NewReader(webpages))
	r.LazyQuotes = true

	for {
		var p Program
		record, err := r.Read()
		if err == io.EOF {
			fmt.Printf("Finished loading programs.\n\n")
			break
		}
		if err != nil {
			log.Printf("Error reading line from program file: %s.  Abandoning attempt to read programs.\n", filename)
			log.Fatal(err)
		}
		p.URL = record[0]
		p.Duration, err = time.ParseDuration(record[1])
		if err != nil {
			fmt.Println("Program rejected.  Invalid duration.")
		}

		fmt.Printf("  Loaded program %.50s to show for %s.\n", p.URL, p.Duration)
		list = append(list, p)
	}

	return list
}

func runProgram(program Program) {

	ip := viper.Get("FIREFOX_IP")
	port := viper.GetInt("FIREFOX_PORT")

	constr := fmt.Sprintf("%s:%d", ip, port)

	conn, err := net.Dial("tcp", constr)
	if err != nil {
		fmt.Printf("Error making network connection to: %s\n", constr)
		fmt.Println("It is possible Firefox needs to be started or restarted.")
		fmt.Println("Pausing for 30s")
		time.Sleep(30 * time.Second) // wait 30 seconds to slow retries
		return
	}

	fmt.Printf("Running program for %s:  %s -> RESULT: ", program.Duration, program.URL)
	fmt.Fprintf(conn, "window.location='%s'\n", program.URL)
	status, err := bufio.NewReader(conn).ReadString('\n')
	if err != nil {
		fmt.Println("ERROR - URL didn't load as desired.")
	}

	var statusParsed interface{}
	err = json.Unmarshal([]byte(status), &statusParsed)

	m := statusParsed.(map[string]interface{})

	if m["result"] == program.URL {
		fmt.Println("OK")
	} else {
		fmt.Println("ERROR - URL didn't load as desired.")
	}

	select {
	case <-time.After(program.Duration):
		// Do nothing.
		Unpause()
	case <-skip:
		fmt.Println("Current program skipped")
		return
	}
}

func Pause() {
	mu.Lock()
	pause = true
	mu.Unlock()
}

func Unpause() {
	mu.Lock()
	pause = false
	mu.Unlock()
}

func IsPaused() bool {
	mu.Lock()
	defer mu.Unlock()

	return pause == true
}

func LoadAndRunLoop() {

	// Load and run the acctive program_file indefinately
	for {
		// We pull filename inside the loop because the
		// configuration can change while our program is running.
		filename := viper.GetString("program_file")
		for IsPaused() {
			fmt.Println("Paused.")
			time.Sleep(1 * time.Second)
		}

		pl := loadProgramList(filename)

		for _, p := range pl {
			for IsPaused() {
				fmt.Println("Program list is paused.")
				time.Sleep(1 * time.Second)
			}
			runProgram(p)
		}

		fmt.Printf("\nLooping back to play program list from beginning\n\n")
	}

}

func PlayHandler(w http.ResponseWriter, r *http.Request) {

	r.ParseForm()
	var p Program
	p.URL = r.Form.Get("url")
	fmt.Printf("URL: %s\n", p.URL)

	d := r.Form.Get("duration")
	fmt.Printf("Duration: %s\n", d)

	var err error
	p.Duration, err = time.ParseDuration(r.Form.Get("duration"))
	if err != nil {
		w.Write([]byte("Program rejected.  Invalid duration.\n"))
		return
	}

	// Needs validation...

	// Now do something with the program.. play it?

	// Stop normal rotation
	Pause()
	skip <- struct{}{}

	go runProgram(p)
	w.Write([]byte("Program accepted\n"))

}

func PauseHandler(w http.ResponseWriter, r *http.Request) {
	Pause()
	log.Println("Pausing from web request")
	w.Write([]byte("Ok, paused.\n"))
}

func ResumeHandler(w http.ResponseWriter, r *http.Request) {
	Unpause()
	log.Println("Unpausing from web request")
	w.Write([]byte("Ok, unpaused.\n"))
}
func SkipHandler(w http.ResponseWriter, r *http.Request) {
	log.Println("Skippingfrom web request")
	Unpause()
	skip <- struct{}{}

	w.Write([]byte("Skipping current programming and resume program list runner from web request.\n"))
}

func readKeyboardLoop() {
	for {
		os.Stdin.Read(make([]byte, 1)) // read a single byte
		fmt.Printf(" >> Got keyboard input, that means you want to move to the next program.  Can do! << \n\n")
		Unpause()
		skip <- struct{}{}
	}
}

// Control channel to stop running programs immediately (yes, global)

var skip = make(chan struct{})
var exitprogram = make(chan struct{})
var pause bool
var mu = &sync.Mutex{}
var version = "0.0.4"

func main() {

	if len(os.Args) > 1 {
		if os.Args[1] == "version" {
			fmt.Println("Gotator version:", version)
			os.Exit(0)
		}
	}
	fmt.Println("Starting gotator: version", version)
	Unpause()

	InitializeConfig()

	go LoadAndRunLoop()
	go readKeyboardLoop()

	if viper.IsSet("apienabled") && viper.Get("apienabled") == true {
		listen_port := ":8080"
		if viper.IsSet("gotator_port") {
			listen_port = ":" + viper.GetString("gotator_port")
		}

		fmt.Printf("Starting API server on port %s.  Notice:  This allows UNAUTHENTICATED remote control of Firefox. set 'apienabled: false' in config.yaml to disable.\n",
			listen_port)

		r := mux.NewRouter()
		r.HandleFunc("/play", PlayHandler)
		r.HandleFunc("/pause", PauseHandler)
		r.HandleFunc("/resume", ResumeHandler)
		r.HandleFunc("/skip", SkipHandler)

		go log.Fatal(http.ListenAndServe(listen_port, r))
	} else {
		fmt.Println("notice: rest API not enabled in configuration and will be unavailable.  set 'apienabled: true' in config.yaml if you want to use it.\n")
		// If we aren't doing http.ListenAndServe() we need to block here or else gotator would exit immediately
		<-exitprogram
	}

}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"os/user"
	"path/filepath"
	"runtime"
	"time"
)

var verbose bool
var quiet bool
var tm bool
var chan_depth int
var buffSize int64
var ignoreMTime bool
var dbpath string
var dataRate float64

func main() {
	runtime.GOMAXPROCS(8)

	var copy_dir string
	var check_dir string
	var err error

	flag.Usage = func() {
		fmt.Printf("Usage of %s:\n", os.Args[0])
		fmt.Printf("%s [-q] [-v] [-tm] [-m] [-depth n] [-buff n] [source [dest]]\n", os.Args[0])
		fmt.Println()
		fmt.Println("This utility checks the contents of files against themselves")
		fmt.Println("and backup copies to try to detect bit failures on disk.  In its")
		fmt.Println("most basic form (with no arguments), it will read all of the files")
		fmt.Println("in the current directory recursively.  As it reads the files, it")
		fmt.Println("creates a database of the files' checksums and modification times.")
		fmt.Println("The next time it is run, any file with the same modification time,")
		fmt.Println("but a different checksum will be flagged.")
		fmt.Println()
		fmt.Println("Given a source parameter, it will check that directory instead.")
		fmt.Println("Given a source and dest, it will do the same database checks,")
		fmt.Println("but also compare the contents of source recursively with dest.")
		fmt.Println()
		fmt.Println("The -tm flag causes it to use the TimeMachine directory that")
		fmt.Println("corresponds to source as the dest.")
		fmt.Println()
		fmt.Println("The -q flag will suppress all output except possible bit failures.")
		fmt.Println()
		fmt.Println("The -v flag will print information about all files checked, even")
		fmt.Println("the ones that are OK.")
		fmt.Println()
		fmt.Println("Normally, if the modification time of a file has changed, it")
		fmt.Println("is not considered a problem if the contents are different.")
		fmt.Println("If the -m flag is given, then files are compared regardless.")
		fmt.Println()
		fmt.Println("The --depth and --buff flags are for tuning.  The default values")
		fmt.Println("should work well in most situations.")
		fmt.Println()

		flag.PrintDefaults()
	}

	flag.CommandLine.SetOutput(os.Stdout)
	flag.BoolVar(&verbose, "v", false, "Print info about files that are OK")
	flag.BoolVar(&quiet, "q", false, "Only print info about checksum errors.  Other file access problems are ignored.")
	flag.BoolVar(&tm, "tm", false, "Compare against TimeMachine")
	flag.BoolVar(&ignoreMTime, "m", false, "Compare files, even if mtime is different.")
	flag.IntVar(&chan_depth, "depth", 10, "Work queue depth")
	flag.Int64Var(&buffSize, "buff", 128*1024*1024, "Size of IO buffer")
	flag.StringVar(&dbpath, "db", "", "Path to database file")
	flag.Float64Var(&dataRate, "rate", 0.0, "Max data rate in bytes/sec")
	flag.Parse()

	// If we're rate limiting, and the buff flag was not explicitly set,
	// the cut down the buff size to 128k
	buffSet := false
	flag.Visit(func(f *flag.Flag) {
		if f.Name == "buff" {
			buffSet = true
		}
	})

	if dataRate != 0.0 && !buffSet {
		buffSize /= 1024
	}

	switch {
	case flag.NArg() == 0:
		// set check_dir to cwd
		check_dir, err = os.Getwd()
		if err != nil {
			log.Println("Cannot get current working directory.  Please supply a directory as an argument.")
			log.Fatal(err)
		}

	case flag.NArg() == 1:
		// use arg as check dir
		check_dir = flag.Arg(0)

	case flag.NArg() == 2:
		// use arg as check dir
		check_dir = flag.Arg(0)
		copy_dir = flag.Arg(1)

	default:
		// print usage, exit
		fmt.Println("Supply one or two directories")
		flag.Usage()
		os.Exit(1)
	}

	check_dir = volPath(check_dir)

	if copy_dir == "" {
		if tm {
			// Set copy dir to latest tm backup
			copy_dir, err := getTMDir()
			if err != nil {
				log.Println("Cannot get latest time machine directory.  Please supply a backup directory.")
				log.Fatal(err)
			}
			copy_dir = filepath.Join(copy_dir, check_dir)
		}
	} else {
		copy_dir = volPath(copy_dir)
	}

	if !quiet {
		fmt.Printf("Checking: %s\n", check_dir)
		if copy_dir != "" {
			fmt.Printf("Comparing with backup at: %s\n", copy_dir)
		}
	}

	if dbpath == "" {
		me, err := user.Current()
		if err != nil {
			log.Fatal(err)
		}

		dbpath = filepath.Join(me.HomeDir, ".sumcheck.sqlite3")
	}
	db, err := newDBConnection(dbpath)
	if err != nil {
		log.Println("Cannot connect to db at " + dbpath)
		log.Fatal(err)
	}
	defer closeDBConnection(db)

	initWalkers()

	start := time.Now()

	final_out := make(chan *fileJob, chan_depth)
	defer close(final_out)

	if copy_dir == "" {
		in1_chan := make(chan *fileJob, chan_depth)
		defer close(in1_chan)
		out1_chan := make(chan *fileJob, chan_depth)
		defer close(out1_chan)

		go Walker(in1_chan, nil, check_dir, "")
		go Calculator(in1_chan, out1_chan, buffSize, dataRate)
		go dbChecker(out1_chan, final_out, db)
	} else {
		in1_chan := make(chan *fileJob, chan_depth)
		defer close(in1_chan)
		in2_chan := make(chan *fileJob, chan_depth)
		defer close(in2_chan)
		out1_chan := make(chan *fileJob, chan_depth)
		defer close(out1_chan)
		out2_chan := make(chan *fileJob, chan_depth)
		defer close(out2_chan)
		comp_out := make(chan *fileJob, chan_depth)
		defer close(comp_out)

		go Walker(in1_chan, in2_chan, check_dir, copy_dir)
		go Calculator(in1_chan, out1_chan, buffSize, dataRate/2.0)
		go Calculator(in2_chan, out2_chan, buffSize, dataRate/2.0)
		go Comparator(out1_chan, out2_chan, comp_out)
		go dbChecker(comp_out, final_out, db)
	}

	var totalIO int64
	var fileCount int64

	for f := range final_out {
		if f == nil {
			break
		}

		if f.IoLen >= 0 {
			totalIO += f.IoLen
			fileCount++
		}
		if f.Err != nil {
			if !quiet || f.Err.code == code_BAD_SUM {
				log.Println(f.Err.Error())
			}
		} else if verbose {
			log.Println(NewError(code_OK, f, "").Error())
		}
	}

	stop := time.Now()

	if !quiet {
		elapsed := stop.Sub(start)
		fmt.Printf("%v bytes in %v (%v bytes per sec.)\n",
			Eng(float64(totalIO)), elapsed.String(),
			Eng(float64(totalIO)/elapsed.Seconds()))
		fmt.Printf("%v files in %v (%v files per sec.)\n",
			Eng(float64(fileCount)), elapsed.String(),
			Eng(float64(fileCount)/elapsed.Seconds()))
	}
}
<commit_msg>Quieter output when no -q or -v flag is given.<commit_after>package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"os/user"
	"path/filepath"
	"runtime"
	"time"
)

var verbose bool
var quiet bool
var tm bool
var chan_depth int
var buffSize int64
var ignoreMTime bool
var dbpath string
var dataRate float64

func main() {
	runtime.GOMAXPROCS(8)

	var copy_dir string
	var check_dir string
	var err error

	flag.Usage = func() {
		fmt.Printf("Usage of %s:\n", os.Args[0])
		fmt.Printf("%s [-q] [-v] [-tm] [-m] [-depth n] [-buff n] [source [dest]]\n", os.Args[0])
		fmt.Println()
		fmt.Println("This utility checks the contents of files against themselves")
		fmt.Println("and backup copies to try to detect bit failures on disk.  In its")
		fmt.Println("most basic form (with no arguments), it will read all of the files")
		fmt.Println("in the current directory recursively.  As it reads the files, it")
		fmt.Println("creates a database of the files' checksums and modification times.")
		fmt.Println("The next time it is run, any file with the same modification time,")
		fmt.Println("but a different checksum will be flagged.")
		fmt.Println()
		fmt.Println("Given a source parameter, it will check that directory instead.")
		fmt.Println("Given a source and dest, it will do the same database checks,")
		fmt.Println("but also compare the contents of source recursively with dest.")
		fmt.Println()
		fmt.Println("The -tm flag causes it to use the TimeMachine directory that")
		fmt.Println("corresponds to source as the dest.")
		fmt.Println()
		fmt.Println("The -q flag will suppress all output except possible bit failures.")
		fmt.Println()
		fmt.Println("The -v flag will print information about all files checked, even")
		fmt.Println("the ones that are OK.")
		fmt.Println()
		fmt.Println("Normally, if the modification time of a file has changed, it")
		fmt.Println("is not considered a problem if the contents are different.")
		fmt.Println("If the -m flag is given, then files are compared regardless.")
		fmt.Println()
		fmt.Println("The --depth and --buff flags are for tuning.  The default values")
		fmt.Println("should work well in most situations.")
		fmt.Println()

		flag.PrintDefaults()
	}

	flag.CommandLine.SetOutput(os.Stdout)
	flag.BoolVar(&verbose, "v", false, "Print info about files that are OK")
	flag.BoolVar(&quiet, "q", false, "Only print info about checksum errors.  Other file access problems are ignored.")
	flag.BoolVar(&tm, "tm", false, "Compare against TimeMachine")
	flag.BoolVar(&ignoreMTime, "m", false, "Compare files, even if mtime is different.")
	flag.IntVar(&chan_depth, "depth", 10, "Work queue depth")
	flag.Int64Var(&buffSize, "buff", 128*1024*1024, "Size of IO buffer")
	flag.StringVar(&dbpath, "db", "", "Path to database file")
	flag.Float64Var(&dataRate, "rate", 0.0, "Max data rate in bytes/sec")
	flag.Parse()

	// If we're rate limiting, and the buff flag was not explicitly set,
	// the cut down the buff size to 128k
	buffSet := false
	flag.Visit(func(f *flag.Flag) {
		if f.Name == "buff" {
			buffSet = true
		}
	})

	if dataRate != 0.0 && !buffSet {
		buffSize /= 1024
	}

	switch {
	case flag.NArg() == 0:
		// set check_dir to cwd
		check_dir, err = os.Getwd()
		if err != nil {
			log.Println("Cannot get current working directory.  Please supply a directory as an argument.")
			log.Fatal(err)
		}

	case flag.NArg() == 1:
		// use arg as check dir
		check_dir = flag.Arg(0)

	case flag.NArg() == 2:
		// use arg as check dir
		check_dir = flag.Arg(0)
		copy_dir = flag.Arg(1)

	default:
		// print usage, exit
		fmt.Println("Supply one or two directories")
		flag.Usage()
		os.Exit(1)
	}

	check_dir = volPath(check_dir)

	if copy_dir == "" {
		if tm {
			// Set copy dir to latest tm backup
			copy_dir, err := getTMDir()
			if err != nil {
				log.Println("Cannot get latest time machine directory.  Please supply a backup directory.")
				log.Fatal(err)
			}
			copy_dir = filepath.Join(copy_dir, check_dir)
		}
	} else {
		copy_dir = volPath(copy_dir)
	}

	if !quiet {
		fmt.Printf("Checking: %s\n", check_dir)
		if copy_dir != "" {
			fmt.Printf("Comparing with backup at: %s\n", copy_dir)
		}
	}

	if dbpath == "" {
		me, err := user.Current()
		if err != nil {
			log.Fatal(err)
		}

		dbpath = filepath.Join(me.HomeDir, ".sumcheck.sqlite3")
	}
	db, err := newDBConnection(dbpath)
	if err != nil {
		log.Println("Cannot connect to db at " + dbpath)
		log.Fatal(err)
	}
	defer closeDBConnection(db)

	initWalkers()

	start := time.Now()

	final_out := make(chan *fileJob, chan_depth)
	defer close(final_out)

	if copy_dir == "" {
		in1_chan := make(chan *fileJob, chan_depth)
		defer close(in1_chan)
		out1_chan := make(chan *fileJob, chan_depth)
		defer close(out1_chan)

		go Walker(in1_chan, nil, check_dir, "")
		go Calculator(in1_chan, out1_chan, buffSize, dataRate)
		go dbChecker(out1_chan, final_out, db)
	} else {
		in1_chan := make(chan *fileJob, chan_depth)
		defer close(in1_chan)
		in2_chan := make(chan *fileJob, chan_depth)
		defer close(in2_chan)
		out1_chan := make(chan *fileJob, chan_depth)
		defer close(out1_chan)
		out2_chan := make(chan *fileJob, chan_depth)
		defer close(out2_chan)
		comp_out := make(chan *fileJob, chan_depth)
		defer close(comp_out)

		go Walker(in1_chan, in2_chan, check_dir, copy_dir)
		go Calculator(in1_chan, out1_chan, buffSize, dataRate/2.0)
		go Calculator(in2_chan, out2_chan, buffSize, dataRate/2.0)
		go Comparator(out1_chan, out2_chan, comp_out)
		go dbChecker(comp_out, final_out, db)
	}

	var totalIO int64
	var fileCount int64

	for f := range final_out {
		if f == nil {
			break
		}

		if f.IoLen >= 0 {
			totalIO += f.IoLen
			fileCount++
		}

		switch {
		case quiet:
			if f.Err != nil && f.Err.code == code_BAD_SUM {
				log.Println(f.Err.Error())
			}

		case verbose:
			if f.Err == nil {
				f.Err = NewError(code_OK, f, "")
			}
			log.Println(f.Err.Error())

		case f.Err != nil:
			if f.Err.code != code_NEW_SUM {
				log.Println(f.Err.Error())
			}
		}
	}

	stop := time.Now()

	if !quiet {
		elapsed := stop.Sub(start)
		fmt.Printf("%v bytes in %v (%v bytes per sec.)\n",
			Eng(float64(totalIO)), elapsed.String(),
			Eng(float64(totalIO)/elapsed.Seconds()))
		fmt.Printf("%v files in %v (%v files per sec.)\n",
			Eng(float64(fileCount)), elapsed.String(),
			Eng(float64(fileCount)/elapsed.Seconds()))
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/coreos/go-etcd/etcd"
	"github.com/facebookgo/flagenv"
	"github.com/jessevdk/go-flags"
	"log"

	"github.com/rcrowley/go-metrics"
	"github.com/rcrowley/go-metrics/librato"
	"regexp"
	"time"
	//	"net/http"
)

type Options struct {
	etcd_server   []string `short:"e" description:"Etcd Server url. Multiple servers can be specified" default:"http://localhost:4001"`
	sensu_server  string   `short:"s" description:Sensu server url default:"http://localhost:8080"`
	key_prefix    string   `short:"k" description:"the prefix to use in etcd for storing check urls" default:"/urlmon"`
	librato_user  string   `short:"u" description:"librato user"`
	librato_token string   `short:"t" description:"librato token"`
}

type Check struct {
	Id           string
	URL          string
	Registry     metrics.Registry
	Content      string
	ContentRegex regexp.Regexp
}

var Checks []Check
var opts Options
var parser = flags.NewParser(&opts, flags.Default)
var UseUpperCaseFlagNames = true

// libratoMetrics starts the goroutine for reporting metrics on this check to librato
func (c *Check) libratoMetrics(done <-chan struct{}) {
	go func() {
		select {
		case <-done:
			return
		default:
			// start the metrics collector should be per-check
			librato.Librato(c.Registry,
				60*time.Second,     // interval
				opts.librato_user,  // account owner email address
				opts.librato_token, // Librato API token
				// This should be per check I think
				c.Id,             // source
				[]float64{95},    // precentiles to send
				time.Millisecond, // time unit
			)
		}
	}()
}

func loadChecks(client *etcd.Client) {
	done := make(chan struct{})

	// create etcd watch chan for reparsing config
	resp, err := client.Get(fmt.Sprint("%s/checks", opts.key_prefix), false, false)
	if err != nil {
		log.Fatalf("Problem fetching Checks from etcd: %s", err)
	}

	for _, n := range resp.Node.Nodes {
		// if its first time and we have no checks load it up
		if len(Checks) > 0 {
			log.Printf("%s: %s\n", n.Key, n.Value)
		} else {
			for _, _ = range Checks {
				done <- struct{}{}
			}
			log.Printf("%s: %s\n", n.Key, n.Value)
		}
	}
}

func main() {
	flagenv.Parse()
	parser.Parse()

	etcdClient := etcd.NewClient(opts.etcd_server)
	watchChan := make(chan *etcd.Response)
	go etcdClient.Watch(fmt.Sprintf("%s/", opts.key_prefix), 0, false, watchChan, nil)
	loadChecks(etcdClient)

	// loop and reload checks when etcd changes
	for {
		<-watchChan
		loadChecks(etcdClient)
	}
}
<commit_msg>update for creating checks<commit_after>package main

import (
	"fmt"
	"github.com/coreos/go-etcd/etcd"
	"github.com/davecgh/go-spew/spew"
	"github.com/facebookgo/flagenv"
	"github.com/jessevdk/go-flags"
	"github.com/rcrowley/go-metrics"
	"github.com/rcrowley/go-metrics/librato"
	"log"
	"path"
	"strings"
	//	"net/http"
	"net/url"
	"os"
	"regexp"
	"time"
)

type Options struct {
	EtcdServer   []string `short:"e" long:"etcd-server" description:"Etcd Server url. Multiple servers can be specified" default:"http://localhost:4001"`
	KeyPrefix    string   `short:"k" long:"etcd-key" description:"the prefix to use in etcd for storing check urls" default:"/urlmon"`
	LibratoUser  string   `short:"u" long:"librato-user" description:"librato user"`
	LibratoToken string   `short:"t" long:"librato-token"  description:"librato token"`
	SensuServer  string   `short:"s" long:"sensu-server" description:"Sensu server url" default:"http://localhost:8080"`
}

type Check struct {
	Id           string
	URL          *url.URL
	Registry     metrics.Registry
	Content      string
	Level        string
	ContentRegex *regexp.Regexp
}

var (
	Checks                []Check
	opts                  Options
	UseUpperCaseFlagNames = true
)

// libratoMetrics starts the goroutine for reporting metrics on this check to librato
func (c *Check) libratoMetrics(done *chan struct{}) {
	go func() {
		select {
		case <-*done:
			return
		default:
			// start the metrics collector should be per-check
			librato.Librato(c.Registry,
				60*time.Second,    // interval
				opts.LibratoUser,  // account owner email address
				opts.LibratoToken, // Librato API token
				// This should be per check I think
				c.Id,             // source
				[]float64{95},    // precentiles to send
				time.Millisecond, // time unit
			)
		}
	}()
}

// createCheck populates check struct with data from etcd check
func createCheck(node *etcd.Node, done *chan struct{}) *Check {
	// create a check
	c := Check{Id: path.Base(node.Key)}
	for _, child := range node.Nodes {
		switch strings.ToUpper(path.Base(child.Key)) {
		case "URL":
			u, err := url.Parse(child.Value)
			if err != nil {
				log.Fatalf("Url isn't valid for key: %s, %s", node.Key, err)
			}
			c.URL = u
		case "CONTENT":
			c.Content = child.Value
		case "REGEX":
			r, err := regexp.Compile(child.Value)
			if err != nil {
				log.Fatalf("couldn't compile regex for Check: %s, %s", node.Key, err)
			}
			c.ContentRegex = r
		case "LEVEL":
			c.Level = strings.ToUpper(node.Value)
		}
	}

	if opts.LibratoUser != "" {
		c.libratoMetrics(done)
	}
	return &c
}

// loadChecks reads etcd and populates the Checks Slice
func loadChecks(client *etcd.Client, done *chan struct{}) {
	// create etcd watch chan for reparsing config
	resp, err := client.Get(fmt.Sprintf("%s/checks", opts.KeyPrefix), true, true)
	if err != nil {
		log.Fatalf("Problem fetching Checks from etcd: %s", err)
	}

	Checks = nil // this clears the slice. we will have to realloc that mem, but gc should get round to it.
	for _, n := range resp.Node.Nodes {
		// these top level nodes should be a directory
		if !n.Dir {
			log.Printf("Error loading config %s is not a dir, skipping", n.Key)
			continue
		}

		// if Checks is populated then we want to shutdown monitors and rebuild
		// only if we should be setting up librato metrics
		if opts.LibratoUser != "" {
			log.Println("waiting for checkers to stop")
			for _, _ = range Checks {
				*done <- struct{}{}
			}
		}
		log.Printf("Loading: %s: %s\n", n.Key, n.Value)
		Checks = append(Checks, *createCheck(n, done))
	}

	spew.Dump(Checks)
}

// Create our Etcd Dir structure
func setupEtcd(client *etcd.Client) {
	for _, path := range []string{opts.KeyPrefix, fmt.Sprintf("%s/checks", opts.KeyPrefix)} {
		if _, err := client.Get(path, false, false); err != nil {
			log.Printf("Creating dir in etcd: %s ", path)
			if _, err := client.CreateDir(path, 0); err != nil {
				log.Fatalf("Couldn't create etcd dir: %s, %s ", err)
			}
		}
	}
}

func main() {
	if _, err := flags.Parse(&opts); err != nil {
		if err.(*flags.Error).Type == flags.ErrHelp {
			os.Exit(0)
		} else {
			log.Println(err)
			os.Exit(1)
		}
	}

	etcdClient := etcd.NewClient(opts.EtcdServer)
	setupEtcd(etcdClient)

	watchChan := make(chan *etcd.Response)
	// setup recursive watch of the keyspace
	go etcdClient.Watch(opts.KeyPrefix, 0, true, watchChan, nil)

	done := make(chan struct{})
	loadChecks(etcdClient, &done)

	// loop and reload checks when etcd changes
	for {
		r := <-watchChan
		log.Printf("Reloading checks from etcd, triggered by '%s' on '%s' with value: '%s' ", r.Action, r.Node.Key, r.Node.Value)
		loadChecks(etcdClient, &done)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"fmt"
	"log"
	"log/syslog"
	"os"
	"os/signal"
	"syscall"

	libdebug "runtime/debug"

	"sync"

	"github.com/gallir/radix.improved/redis"
	"github.com/gallir/smart-relayer/lib"
	"github.com/gallir/smart-relayer/redis/cluster"
	"github.com/gallir/smart-relayer/redis/fh"
	"github.com/gallir/smart-relayer/redis/fs"
	"github.com/gallir/smart-relayer/redis/radix"
	"github.com/gallir/smart-relayer/redis/rsqs"
)

const (
	version = "9.0.0-dev"
)

var (
	mutex          sync.Mutex
	relayers       = make(map[string]lib.Relayer)
	totalRelayers  = 0
	relayersConfig *lib.Config
	done           = make(chan bool)
	reloadSig      = make(chan os.Signal, 1)
	exitSig        = make(chan os.Signal, 1)
)

func getNewServer(conf lib.RelayerConfig) (srv lib.Relayer, err error) {
	switch conf.Protocol {
	case "redis", "redis2":
		srv, err = redis2.New(conf, done)
	case "redis-cluster", "redis-plus":
		srv, err = cluster.New(conf, done)
	case "firehose":
		srv, err = fh.New(conf, done)
	case "sqs":
		srv, err = rsqs.New(conf, done)
	case "fs":
		srv, err = fs.New(conf, done)
	default:
		err = errors.New("no valid option")
	}
	return
}

func startOrReload() bool {
	mutex.Lock()
	defer mutex.Unlock()

	// Check config is OK
	newConf, err := lib.ReadConfig(lib.GlobalConfig.ConfigFileName)
	if err != nil {
		log.Println("Bad configuration", err)
		return false
	}

	if newConf.GOGC > 100 {
		libdebug.SetGCPercent(newConf.GOGC)
		log.Println("Set GCPercent to", newConf.GOGC)
	} else {
		libdebug.SetGCPercent(100)
	}

	redis.UsePool = newConf.BufferPoolSize
	if redis.UsePool > 0 {
		log.Println("Set UsePool to", redis.UsePool)
	}

	lib.Debugf("%#v", newConf.Relayer)

	newEndpoints := make(map[string]bool)

	for _, conf := range newConf.Relayer {
		endpoint, ok := relayers[conf.Listen]
		newEndpoints[conf.Listen] = true
		if !ok {
			// Start a new relayer
			r, err := getNewServer(conf)
			if err != nil {
				log.Fatalln("Error starting relayer", conf.Protocol, conf.URL, err)
			}
			lib.Debugf("Starting new relayer from %s to %s", conf.Listen, conf.URL)
			totalRelayers++
			if e := r.Start(); e == nil {
				relayers[conf.Listen] = r
			}
		} else {
			// The relayer exists, reload it
			err := endpoint.Reload(&conf)
			if err != nil {
				log.Fatalln("Error reloading", conf.Protocol, conf.URL, err)
			}
		}
	}

	for endpoint, r := range relayers {
		_, ok := newEndpoints[endpoint]
		if !ok {
			log.Printf("Deleting old endpoint %s", endpoint)
			delete(relayers, endpoint)
			r.Exit()
		}
	}

	return true
}

func main() {
	// Force a high number of file descriptoir, if possible
	var rLimit syscall.Rlimit
	e := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit)
	if e == nil {
		rLimit.Cur = 65536
		syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit)
	}

	// Show version and exit
	if lib.GlobalConfig.ShowVersion {
		fmt.Println("smart-relayer version", version)
		fmt.Printf("Max files %d/%d\n", rLimit.Cur, rLimit.Max)
		os.Exit(0)
	}

	if !lib.GlobalConfig.Debug {
		logwriter, e := syslog.New(syslog.LOG_INFO|syslog.LOG_USER, "smart-relayer")
		if e == nil {
			log.SetFlags(0)
			log.SetOutput(logwriter)
		}
	}

	// Listen for reload signals
	signal.Notify(reloadSig, syscall.SIGHUP, syscall.SIGUSR1, syscall.SIGUSR2)
	signal.Notify(exitSig, syscall.SIGINT, syscall.SIGKILL, os.Interrupt, syscall.SIGTERM)

	// Reload config
	go func() {
		for {
			_ = <-reloadSig
			startOrReload()
		}
	}()

	// Exit
	go func() {
		for {
			s := <-exitSig
			log.Printf("Signal %d received, exiting", s)
			for _, r := range relayers {
				r.Exit()
			}
		}
	}()

	if !startOrReload() {
		os.Exit(1)
	}

	for i := 0; i < totalRelayers; i++ {
		<-done
	}
	os.Exit(0)
}
<commit_msg>Version 9.0.1-dev<commit_after>package main

import (
	"errors"
	"fmt"
	"log"
	"log/syslog"
	"os"
	"os/signal"
	"syscall"

	libdebug "runtime/debug"

	"sync"

	"github.com/gallir/radix.improved/redis"
	"github.com/gallir/smart-relayer/lib"
	"github.com/gallir/smart-relayer/redis/cluster"
	"github.com/gallir/smart-relayer/redis/fh"
	"github.com/gallir/smart-relayer/redis/fs"
	"github.com/gallir/smart-relayer/redis/radix"
	"github.com/gallir/smart-relayer/redis/rsqs"
)

const (
	version = "9.0.1-dev"
)

var (
	mutex          sync.Mutex
	relayers       = make(map[string]lib.Relayer)
	totalRelayers  = 0
	relayersConfig *lib.Config
	done           = make(chan bool)
	reloadSig      = make(chan os.Signal, 1)
	exitSig        = make(chan os.Signal, 1)
)

func getNewServer(conf lib.RelayerConfig) (srv lib.Relayer, err error) {
	switch conf.Protocol {
	case "redis", "redis2":
		srv, err = redis2.New(conf, done)
	case "redis-cluster", "redis-plus":
		srv, err = cluster.New(conf, done)
	case "firehose":
		srv, err = fh.New(conf, done)
	case "sqs":
		srv, err = rsqs.New(conf, done)
	case "fs":
		srv, err = fs.New(conf, done)
	default:
		err = errors.New("no valid option")
	}
	return
}

func startOrReload() bool {
	mutex.Lock()
	defer mutex.Unlock()

	// Check config is OK
	newConf, err := lib.ReadConfig(lib.GlobalConfig.ConfigFileName)
	if err != nil {
		log.Println("Bad configuration", err)
		return false
	}

	if newConf.GOGC > 100 {
		libdebug.SetGCPercent(newConf.GOGC)
		log.Println("Set GCPercent to", newConf.GOGC)
	} else {
		libdebug.SetGCPercent(100)
	}

	redis.UsePool = newConf.BufferPoolSize
	if redis.UsePool > 0 {
		log.Println("Set UsePool to", redis.UsePool)
	}

	lib.Debugf("%#v", newConf.Relayer)

	newEndpoints := make(map[string]bool)

	for _, conf := range newConf.Relayer {
		endpoint, ok := relayers[conf.Listen]
		newEndpoints[conf.Listen] = true
		if !ok {
			// Start a new relayer
			r, err := getNewServer(conf)
			if err != nil {
				log.Fatalln("Error starting relayer", conf.Protocol, conf.URL, err)
			}
			lib.Debugf("Starting new relayer from %s to %s", conf.Listen, conf.URL)
			totalRelayers++
			if e := r.Start(); e == nil {
				relayers[conf.Listen] = r
			}
		} else {
			// The relayer exists, reload it
			err := endpoint.Reload(&conf)
			if err != nil {
				log.Fatalln("Error reloading", conf.Protocol, conf.URL, err)
			}
		}
	}

	for endpoint, r := range relayers {
		_, ok := newEndpoints[endpoint]
		if !ok {
			log.Printf("Deleting old endpoint %s", endpoint)
			delete(relayers, endpoint)
			r.Exit()
		}
	}

	return true
}

func main() {
	// Force a high number of file descriptoir, if possible
	var rLimit syscall.Rlimit
	e := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit)
	if e == nil {
		rLimit.Cur = 65536
		syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit)
	}

	// Show version and exit
	if lib.GlobalConfig.ShowVersion {
		fmt.Println("smart-relayer version", version)
		fmt.Printf("Max files %d/%d\n", rLimit.Cur, rLimit.Max)
		os.Exit(0)
	}

	if !lib.GlobalConfig.Debug {
		logwriter, e := syslog.New(syslog.LOG_INFO|syslog.LOG_USER, "smart-relayer")
		if e == nil {
			log.SetFlags(0)
			log.SetOutput(logwriter)
		}
	}

	// Listen for reload signals
	signal.Notify(reloadSig, syscall.SIGHUP, syscall.SIGUSR1, syscall.SIGUSR2)
	signal.Notify(exitSig, syscall.SIGINT, syscall.SIGKILL, os.Interrupt, syscall.SIGTERM)

	// Reload config
	go func() {
		for {
			_ = <-reloadSig
			startOrReload()
		}
	}()

	// Exit
	go func() {
		for {
			s := <-exitSig
			log.Printf("Signal %d received, exiting", s)
			for _, r := range relayers {
				r.Exit()
			}
		}
	}()

	if !startOrReload() {
		os.Exit(1)
	}

	for i := 0; i < totalRelayers; i++ {
		<-done
	}
	os.Exit(0)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/kch42/gomcmap/mcmap"
	"github.com/mattn/go-gtk/gdk"
	"github.com/mattn/go-gtk/glib"
	"github.com/mattn/go-gtk/gtk"
	"os"
)

type GUI struct {
	window     *gtk.Window
	statusbar  *gtk.Statusbar
	showbiomes *gtk.CheckButton

	statusContext uint

	mapw *MapWidget
}

func (g *GUI) openWorldDlg() {
	dlg := gtk.NewFileChooserDialog("Open World (region directory)", g.window, gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, "Open Region dir", gtk.RESPONSE_OK, "Cancel", gtk.RESPONSE_CANCEL)
	if dlg.Run() == gtk.RESPONSE_OK {
		g.openWorld(dlg.GetFilename())
	}
	dlg.Destroy()
}

func (g *GUI) openWorld(path string) {
	region, err := mcmap.OpenRegion(path, false)
	if err != nil {
		dlg := gtk.NewMessageDialog(g.window, gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_WARNING, gtk.BUTTONS_OK, "Could not load world %s:\n%s", path, err.Error())
		dlg.Run()
		dlg.Destroy()
	}

	go g.mapw.setRegion(region)
}

func (g *GUI) aboutDlg() {
	dlg := gtk.NewAboutDialog()
	dlg.SetName("biome-editor")
	dlg.SetVersion("α")
	dlg.SetCopyright("© 2013 by Kevin Chabowski")
	dlg.SetAuthors([]string{"Kevin Chabowski <kevin@kch42.de>"})
	dlg.SetLicense(`Copyright (c) 2013 Kevin Chabowski

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
`)
	dlg.Run()
	dlg.Destroy()
}

func (g *GUI) mkMenuBar() *gtk.MenuBar {
	menubar := gtk.NewMenuBar()

	fileMenu := gtk.NewMenu()

	open := gtk.NewMenuItemWithLabel("Open")
	open.Connect("activate", g.openWorldDlg)
	fileMenu.Append(open)

	if quickopen, ok := g.mkQuickOpen(); ok {
		quickopenItem := gtk.NewMenuItemWithLabel("Open Map")
		quickopenItem.SetSubmenu(quickopen)
		fileMenu.Append(quickopenItem)
	}

	save := gtk.NewMenuItemWithLabel("Save")
	save.Connect("activate", g.save)
	fileMenu.Append(save)

	quit := gtk.NewMenuItemWithLabel("Quit")
	quit.Connect("activate", g.exitApp)
	fileMenu.Append(quit)

	fileMenuItem := gtk.NewMenuItemWithLabel("File")
	fileMenuItem.SetSubmenu(fileMenu)
	menubar.Append(fileMenuItem)

	/*editMenu := gtk.NewMenu()

	undo := gtk.NewMenuItemWithLabel("Undo")
	undo.Connect("activate", g.undo)
	editMenu.Append(undo)

	editMenuItem := gtk.NewMenuItemWithLabel("Edit")
	editMenuItem.SetSubmenu(editMenu)
	menubar.Append(editMenuItem)*/

	helpMenu := gtk.NewMenu()

	about := gtk.NewMenuItemWithLabel("About")
	about.Connect("activate", g.aboutDlg)
	helpMenu.Append(about)

	helpMenuItem := gtk.NewMenuItemWithLabel("Help")
	helpMenuItem.SetSubmenu(helpMenu)
	menubar.Append(helpMenuItem)

	return menubar
}

func (g *GUI) save() {
	g.mapw.Save()
}

func (g *GUI) mkQuickOpen() (*gtk.Menu, bool) {
	maps := allMaps()
	if (maps == nil) || (len(maps) == 0) {
		return nil, false
	}

	menu := gtk.NewMenu()
	for name, p := range maps {
		mitem := gtk.NewMenuItemWithLabel(name)
		p2 := p
		mitem.Connect("activate", func() { g.openWorld(p2) })
		menu.Append(mitem)
	}

	return menu, true
}

func labelCustomFont(text, font string) *gtk.Label {
	label := gtk.NewLabel(text)
	label.ModifyFontEasy(font)
	return label
}

func (g *GUI) mkToolbox() *gtk.ScrolledWindow {
	vbox := gtk.NewVBox(false, 0)

	vbox.PackStart(labelCustomFont("Tools", "Sans Bold 14"), false, false, 3)

	g.showbiomes = gtk.NewCheckButtonWithLabel("Show Biomes")
	g.showbiomes.SetActive(true)
	g.showbiomes.Connect("toggled", g.showbiomesToggled)
	vbox.PackStart(g.showbiomes, false, false, 3)

	fill := gtk.NewRadioButtonWithLabel(nil, "Fill")
	fill.SetActive(true)
	fill.Connect("toggled", g.mkUpdateToolFx(fill, NewFillTool()))

	draw := gtk.NewRadioButtonWithLabel(fill.GetGroup(), "Draw")
	drawRadius := gtk.NewSpinButtonWithRange(1, 20, 1)
	drawHBox := gtk.NewHBox(false, 0)
	drawHBox.PackStart(draw, true, true, 0)
	drawHBox.PackStart(gtk.NewLabel("Radius:"), false, false, 3)
	drawHBox.PackEnd(drawRadius, false, false, 3)
	draw.Connect("toggled", g.mkUpdateToolFx(draw, NewDrawTool(func() int { return int(drawRadius.GetValue()) })))

	vbox.PackStart(fill, false, false, 3)
	vbox.PackStart(drawHBox, false, false, 3)

	vbox.PackStart(gtk.NewHSeparator(), false, false, 3)
	vbox.PackStart(labelCustomFont("Biomes", "Sans Bold 14"), false, false, 3)

	var grp *glib.SList
	for _, bio := range bioList {
		biohbox := gtk.NewHBox(false, 0)
		cbox := colorBox(bioColors[bio])
		cbox.SetSizeRequest(20, 20)
		biohbox.PackStart(cbox, false, false, 3)
		rbutton := gtk.NewRadioButtonWithLabel(grp, bio.String())
		grp = rbutton.GetGroup()
		rbutton.Connect("toggled", g.mkUpdateBiomeFx(rbutton, bio))
		biohbox.PackEnd(rbutton, true, true, 3)
		vbox.PackStart(biohbox, false, false, 3)
	}

	scrolled := gtk.NewScrolledWindow(nil, nil)
	scrolled.SetPolicy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
	scrolled.AddWithViewPort(vbox)
	return scrolled
}

func (g *GUI) Init() {
	g.window = gtk.NewWindow(gtk.WINDOW_TOPLEVEL)
	g.window.SetTitle("Biome Editor")

	menubar := g.mkMenuBar()
	vbox := gtk.NewVBox(false, 0)
	vbox.PackStart(menubar, false, false, 0)

	hbox := gtk.NewHBox(false, 0)

	g.mapw = NewMapWidget(g.reportError, g.updateInfo)
	hbox.PackStart(g.mapw.DArea(), true, true, 3)

	toolbox := g.mkToolbox()
	hbox.PackEnd(toolbox, false, false, 3)

	vbox.PackStart(hbox, true, true, 0)

	g.statusbar = gtk.NewStatusbar()
	g.statusContext = g.statusbar.GetContextId("mapinfo")
	vbox.PackEnd(g.statusbar, false, false, 0)

	g.window.Add(vbox)
	g.window.SetDefaultSize(800, 600)

	g.window.Connect("destroy", g.exitApp)

	g.setTool(NewFillTool())
}

func (g *GUI) reportError(msg string) {
	dlg := gtk.NewMessageDialog(g.window, gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, msg)
	dlg.Run()
	dlg.Destroy()
	os.Exit(1)
}

func (g *GUI) updateInfo(x, z int, bio mcmap.Biome) {
	g.statusbar.Pop(g.statusContext)
	g.statusbar.Push(g.statusContext, fmt.Sprintf("X:%d, Z:%d, Biome:%s", x, z, bio))
}

func (g *GUI) mkUpdateToolFx(rb *gtk.RadioButton, t Tool) func() {
	return func() {
		if rb.GetActive() {
			g.setTool(t)
		}
	}
}

func (g *GUI) mkUpdateBiomeFx(rb *gtk.RadioButton, bio mcmap.Biome) func() {
	return func() {
		if rb.GetActive() {
			g.setBiome(bio)
		}
	}
}

func (g *GUI) setTool(t Tool) {
	g.mapw.SetTool(t)
}

func (g *GUI) setBiome(bio mcmap.Biome) {
	g.mapw.SetBiome(bio)
}

func (g *GUI) showbiomesToggled() {
	g.mapw.SetShowBiomes(g.showbiomes.GetActive())
}

/*func (g *GUI) undo() {
	fmt.Println("Undo")
}*/

func (g *GUI) Show() {
	g.window.ShowAll()
}

func (g *GUI) exitApp() {
	gtk.MainQuit()
}

func main() {
	glib.ThreadInit(nil)
	gdk.ThreadsInit()
	gdk.ThreadsEnter()
	gtk.Init(nil)

	gui := new(GUI)
	gui.Init()
	gui.Show()

	gtk.Main()
	gdk.ThreadsLeave()
}
<commit_msg>Renamed Toolbox to Sidebar.<commit_after>package main

import (
	"fmt"
	"github.com/kch42/gomcmap/mcmap"
	"github.com/mattn/go-gtk/gdk"
	"github.com/mattn/go-gtk/glib"
	"github.com/mattn/go-gtk/gtk"
	"os"
)

type GUI struct {
	window     *gtk.Window
	statusbar  *gtk.Statusbar
	showbiomes *gtk.CheckButton

	statusContext uint

	mapw *MapWidget
}

func (g *GUI) openWorldDlg() {
	dlg := gtk.NewFileChooserDialog("Open World (region directory)", g.window, gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, "Open Region dir", gtk.RESPONSE_OK, "Cancel", gtk.RESPONSE_CANCEL)
	if dlg.Run() == gtk.RESPONSE_OK {
		g.openWorld(dlg.GetFilename())
	}
	dlg.Destroy()
}

func (g *GUI) openWorld(path string) {
	region, err := mcmap.OpenRegion(path, false)
	if err != nil {
		dlg := gtk.NewMessageDialog(g.window, gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_WARNING, gtk.BUTTONS_OK, "Could not load world %s:\n%s", path, err.Error())
		dlg.Run()
		dlg.Destroy()
	}

	go g.mapw.setRegion(region)
}

func (g *GUI) aboutDlg() {
	dlg := gtk.NewAboutDialog()
	dlg.SetName("biome-editor")
	dlg.SetVersion("α")
	dlg.SetCopyright("© 2013 by Kevin Chabowski")
	dlg.SetAuthors([]string{"Kevin Chabowski <kevin@kch42.de>"})
	dlg.SetLicense(`Copyright (c) 2013 Kevin Chabowski

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
`)
	dlg.Run()
	dlg.Destroy()
}

func (g *GUI) mkMenuBar() *gtk.MenuBar {
	menubar := gtk.NewMenuBar()

	fileMenu := gtk.NewMenu()

	open := gtk.NewMenuItemWithLabel("Open")
	open.Connect("activate", g.openWorldDlg)
	fileMenu.Append(open)

	if quickopen, ok := g.mkQuickOpen(); ok {
		quickopenItem := gtk.NewMenuItemWithLabel("Open Map")
		quickopenItem.SetSubmenu(quickopen)
		fileMenu.Append(quickopenItem)
	}

	save := gtk.NewMenuItemWithLabel("Save")
	save.Connect("activate", g.save)
	fileMenu.Append(save)

	quit := gtk.NewMenuItemWithLabel("Quit")
	quit.Connect("activate", g.exitApp)
	fileMenu.Append(quit)

	fileMenuItem := gtk.NewMenuItemWithLabel("File")
	fileMenuItem.SetSubmenu(fileMenu)
	menubar.Append(fileMenuItem)

	/*editMenu := gtk.NewMenu()

	undo := gtk.NewMenuItemWithLabel("Undo")
	undo.Connect("activate", g.undo)
	editMenu.Append(undo)

	editMenuItem := gtk.NewMenuItemWithLabel("Edit")
	editMenuItem.SetSubmenu(editMenu)
	menubar.Append(editMenuItem)*/

	helpMenu := gtk.NewMenu()

	about := gtk.NewMenuItemWithLabel("About")
	about.Connect("activate", g.aboutDlg)
	helpMenu.Append(about)

	helpMenuItem := gtk.NewMenuItemWithLabel("Help")
	helpMenuItem.SetSubmenu(helpMenu)
	menubar.Append(helpMenuItem)

	return menubar
}

func (g *GUI) save() {
	g.mapw.Save()
}

func (g *GUI) mkQuickOpen() (*gtk.Menu, bool) {
	maps := allMaps()
	if (maps == nil) || (len(maps) == 0) {
		return nil, false
	}

	menu := gtk.NewMenu()
	for name, p := range maps {
		mitem := gtk.NewMenuItemWithLabel(name)
		p2 := p
		mitem.Connect("activate", func() { g.openWorld(p2) })
		menu.Append(mitem)
	}

	return menu, true
}

func labelCustomFont(text, font string) *gtk.Label {
	label := gtk.NewLabel(text)
	label.ModifyFontEasy(font)
	return label
}

func (g *GUI) mkSidebar() *gtk.ScrolledWindow {
	vbox := gtk.NewVBox(false, 0)

	vbox.PackStart(labelCustomFont("Tools", "Sans Bold 14"), false, false, 3)

	g.showbiomes = gtk.NewCheckButtonWithLabel("Show Biomes")
	g.showbiomes.SetActive(true)
	g.showbiomes.Connect("toggled", g.showbiomesToggled)
	vbox.PackStart(g.showbiomes, false, false, 3)

	fill := gtk.NewRadioButtonWithLabel(nil, "Fill")
	fill.SetActive(true)
	fill.Connect("toggled", g.mkUpdateToolFx(fill, NewFillTool()))

	draw := gtk.NewRadioButtonWithLabel(fill.GetGroup(), "Draw")
	drawRadius := gtk.NewSpinButtonWithRange(1, 20, 1)
	drawHBox := gtk.NewHBox(false, 0)
	drawHBox.PackStart(draw, true, true, 0)
	drawHBox.PackStart(gtk.NewLabel("Radius:"), false, false, 3)
	drawHBox.PackEnd(drawRadius, false, false, 3)
	draw.Connect("toggled", g.mkUpdateToolFx(draw, NewDrawTool(func() int { return int(drawRadius.GetValue()) })))

	vbox.PackStart(fill, false, false, 3)
	vbox.PackStart(drawHBox, false, false, 3)

	vbox.PackStart(gtk.NewHSeparator(), false, false, 3)
	vbox.PackStart(labelCustomFont("Biomes", "Sans Bold 14"), false, false, 3)

	var grp *glib.SList
	for _, bio := range bioList {
		biohbox := gtk.NewHBox(false, 0)
		cbox := colorBox(bioColors[bio])
		cbox.SetSizeRequest(20, 20)
		biohbox.PackStart(cbox, false, false, 3)
		rbutton := gtk.NewRadioButtonWithLabel(grp, bio.String())
		grp = rbutton.GetGroup()
		rbutton.Connect("toggled", g.mkUpdateBiomeFx(rbutton, bio))
		biohbox.PackEnd(rbutton, true, true, 3)
		vbox.PackStart(biohbox, false, false, 3)
	}

	scrolled := gtk.NewScrolledWindow(nil, nil)
	scrolled.SetPolicy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
	scrolled.AddWithViewPort(vbox)
	return scrolled
}

func (g *GUI) Init() {
	g.window = gtk.NewWindow(gtk.WINDOW_TOPLEVEL)
	g.window.SetTitle("Biome Editor")

	menubar := g.mkMenuBar()
	vbox := gtk.NewVBox(false, 0)
	vbox.PackStart(menubar, false, false, 0)

	hbox := gtk.NewHBox(false, 0)

	g.mapw = NewMapWidget(g.reportError, g.updateInfo)
	hbox.PackStart(g.mapw.DArea(), true, true, 3)

	sidebar := g.mkSidebar()
	hbox.PackEnd(sidebar, false, false, 3)

	vbox.PackStart(hbox, true, true, 0)

	g.statusbar = gtk.NewStatusbar()
	g.statusContext = g.statusbar.GetContextId("mapinfo")
	vbox.PackEnd(g.statusbar, false, false, 0)

	g.window.Add(vbox)
	g.window.SetDefaultSize(800, 600)

	g.window.Connect("destroy", g.exitApp)

	g.setTool(NewFillTool())
}

func (g *GUI) reportError(msg string) {
	dlg := gtk.NewMessageDialog(g.window, gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, msg)
	dlg.Run()
	dlg.Destroy()
	os.Exit(1)
}

func (g *GUI) updateInfo(x, z int, bio mcmap.Biome) {
	g.statusbar.Pop(g.statusContext)
	g.statusbar.Push(g.statusContext, fmt.Sprintf("X:%d, Z:%d, Biome:%s", x, z, bio))
}

func (g *GUI) mkUpdateToolFx(rb *gtk.RadioButton, t Tool) func() {
	return func() {
		if rb.GetActive() {
			g.setTool(t)
		}
	}
}

func (g *GUI) mkUpdateBiomeFx(rb *gtk.RadioButton, bio mcmap.Biome) func() {
	return func() {
		if rb.GetActive() {
			g.setBiome(bio)
		}
	}
}

func (g *GUI) setTool(t Tool) {
	g.mapw.SetTool(t)
}

func (g *GUI) setBiome(bio mcmap.Biome) {
	g.mapw.SetBiome(bio)
}

func (g *GUI) showbiomesToggled() {
	g.mapw.SetShowBiomes(g.showbiomes.GetActive())
}

/*func (g *GUI) undo() {
	fmt.Println("Undo")
}*/

func (g *GUI) Show() {
	g.window.ShowAll()
}

func (g *GUI) exitApp() {
	gtk.MainQuit()
}

func main() {
	glib.ThreadInit(nil)
	gdk.ThreadsInit()
	gdk.ThreadsEnter()
	gtk.Init(nil)

	gui := new(GUI)
	gui.Init()
	gui.Show()

	gtk.Main()
	gdk.ThreadsLeave()
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"html/template"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/exec"
	"time"
)

var streams []stream
var commands []*exec.Cmd

type stream struct {
	Name   string
	Stream string
	Image  string
}

func main() {
	//Read in urls of webcams from configuration file
	data, err := ioutil.ReadFile("streams.json")
	if err != nil {
		panic(err)
	}

	if err := json.Unmarshal(data, &streams); err != nil {
		panic(err)
	}

	//Startup webserver to listen to commands to execute video player
	http.HandleFunc("/", serveIndex)
	http.HandleFunc("/all", serveAll)
	http.HandleFunc("/pick", serveOne)
	http.HandleFunc("/stop", serveStop)
	// showAll()
	log.Println("server started...")
	log.Fatal(http.ListenAndServe(":2000", nil))
}

func renderWebsite(w http.ResponseWriter) {
	const tpl = `
<!DOCTYPE html>

		<meta charset="UTF-8">
		<title>Zoo Cam Viewer</title>

All

Stop

		{{range .Streams}}
{{ .Name }}

{{else}}
no streams
{{end}}

`
	t, err := template.New("webpage").Parse(tpl)
	if err != nil {
		panic(err)
	}
	t.Execute(w, struct{ Streams []stream }{Streams: streams})
}

func serveIndex(w http.ResponseWriter, r *http.Request) {
	//Serve up basic website with icons to choose from with "all" option
	renderWebsite(w)
}

func serveAll(w http.ResponseWriter, r *http.Request) {
	//Close all existing processes, and fire up all the videos
	showAll()
	renderWebsite(w)
}

func serveOne(w http.ResponseWriter, r *http.Request) {
	//Close all existing processes, and fire up the single video passed in
	name := r.FormValue("name")
	log.Println("got stream name of " + name)
	for _, stream := range streams {
		if stream.Name == name {
			log.Println("Starting stream " + stream.Name)
			showOne(stream)
		}
	}
	renderWebsite(w)
}

func serveStop(w http.ResponseWriter, r *http.Request) {
	killAll()
	renderWebsite(w)
}

func showAll() {
	killAll()
	width := 1900
	height := 1200
	streamCount := len(streams)

	//Determine how many streams we have to make even boxed grids
	boxes := 1
	for ; boxes*boxes < streamCount; boxes++ {
	}

	startWidth := 0
	startHeight := 0
	widthStep := width / boxes
	heightStep := height / boxes
	//We now have a box X box width screen (say 3x3), so split the screen appropriately
	for index, s := range streams {
		endWidth := startWidth + (index * widthStep)
		endHeight := startHeight + (index * heightStep)
		log.Printf("end width is %v and end height is %v\n", endWidth, endHeight)
		cmd := exec.Command("omxplayer", "--win", fmt.Sprintf("%v,%v,%v,%v", startWidth, startHeight, endWidth, endHeight), s.Stream)
		// cmd := exec.Command("mplayer", s.Stream)
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		commands = append(commands, cmd)
		cmd.Start()
		time.Sleep(1 * time.Second)
	}
}

func showOne(s stream) {
	killAll()
	//Startup in fullscreen
	cmd := exec.Command("omxplayer", "-b", s.Stream)
	// cmd := exec.Command("mplayer", s.Stream)
	cmd.Start()
	commands = append(commands, cmd)
}

func killAll() {
	log.Println("killing all existing streams")
	for _, proc := range commands {
		proc.Process.Kill()
	}
}
<commit_msg>fixing box size determination<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"html/template"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/exec"
	"time"
)

var streams []stream
var commands []*exec.Cmd

type stream struct {
	Name   string
	Stream string
	Image  string
}

func main() {
	//Read in urls of webcams from configuration file
	data, err := ioutil.ReadFile("streams.json")
	if err != nil {
		panic(err)
	}

	if err := json.Unmarshal(data, &streams); err != nil {
		panic(err)
	}

	//Startup webserver to listen to commands to execute video player
	http.HandleFunc("/", serveIndex)
	http.HandleFunc("/all", serveAll)
	http.HandleFunc("/pick", serveOne)
	http.HandleFunc("/stop", serveStop)
	// showAll()
	log.Println("server started...")
	log.Fatal(http.ListenAndServe(":2000", nil))
}

func renderWebsite(w http.ResponseWriter) {
	const tpl = `
<!DOCTYPE html>

		<meta charset="UTF-8">
		<title>Zoo Cam Viewer</title>

All

Stop

		{{range .Streams}}
{{ .Name }}

{{else}}
no streams
{{end}}

`
	t, err := template.New("webpage").Parse(tpl)
	if err != nil {
		panic(err)
	}
	t.Execute(w, struct{ Streams []stream }{Streams: streams})
}

func serveIndex(w http.ResponseWriter, r *http.Request) {
	//Serve up basic website with icons to choose from with "all" option
	renderWebsite(w)
}

func serveAll(w http.ResponseWriter, r *http.Request) {
	//Close all existing processes, and fire up all the videos
	showAll()
	renderWebsite(w)
}

func serveOne(w http.ResponseWriter, r *http.Request) {
	//Close all existing processes, and fire up the single video passed in
	name := r.FormValue("name")
	log.Println("got stream name of " + name)
	for _, stream := range streams {
		if stream.Name == name {
			log.Println("Starting stream " + stream.Name)
			showOne(stream)
		}
	}
	renderWebsite(w)
}

func serveStop(w http.ResponseWriter, r *http.Request) {
	killAll()
	renderWebsite(w)
}

func showAll() {
	killAll()
	width := 1920
	height := 1080
	streamCount := len(streams)

	//Determine how many streams we have to make even boxed grids
	boxes := 1
	for ; boxes*boxes < streamCount; boxes++ {
	}

	startWidth := 0
	startHeight := 0
	widthStep := width / boxes
	heightStep := height / boxes
	//We now have a box X box width screen (say 3x3), so split the screen appropriately
	for index, s := range streams {
		endWidth := startWidth + ((index + 1) * widthStep)
		endHeight := startHeight + ((index + 1) * heightStep)
		log.Printf("end width is %v and end height is %v\n", endWidth, endHeight)
		log.Printf("dimensions of window: %v,%v,%v,%v", startWidth, startHeight, endWidth, endHeight)
		cmd := exec.Command("omxplayer", "--win", fmt.Sprintf("%v,%v,%v,%v", startWidth, startHeight, endWidth, endHeight), s.Stream)
		// cmd := exec.Command("mplayer", s.Stream)
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		commands = append(commands, cmd)
		cmd.Start()
		time.Sleep(3 * time.Second)
	}
}

func showOne(s stream) {
	killAll()
	//Startup in fullscreen
	cmd := exec.Command("omxplayer", "-b", s.Stream)
	// cmd := exec.Command("mplayer", s.Stream)
	cmd.Start()
	commands = append(commands, cmd)
}

func killAll() {
	log.Println("killing all existing streams")
	for _, proc := range commands {
		proc.Process.Kill()
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"

	"github.com/hashicorp/terraform/plugin"
	"github.com/mitchellh/cli"
	"github.com/mitchellh/panicwrap"
	"github.com/mitchellh/prefixedio"
)

func main() {
	os.Exit(realMain())
}

func realMain() int {
	var wrapConfig panicwrap.WrapConfig

	if !panicwrap.Wrapped(&wrapConfig) {
		// Determine where logs should go in general (requested by the user)
		logWriter, err := logOutput()
		if err != nil {
			fmt.Fprintf(os.Stderr, "Couldn't setup log output: %s", err)
			return 1
		}
		if logWriter == nil {
			logWriter = ioutil.Discard
		}

		// We always send logs to a temporary file that we use in case
		// there is a panic. Otherwise, we delete it.
		logTempFile, err := ioutil.TempFile("", "terraform-log")
		if err != nil {
			fmt.Fprintf(os.Stderr, "Couldn't setup logging tempfile: %s", err)
			return 1
		}
		defer os.Remove(logTempFile.Name())
		defer logTempFile.Close()

		// Tell the logger to log to this file
		os.Setenv(EnvLog, "")
		os.Setenv(EnvLogFile, "")

		// Setup the prefixed readers that send data properly to
		// stdout/stderr.
		outR, outW := io.Pipe()
		go copyOutput(outR)

		// Create the configuration for panicwrap and wrap our executable
		wrapConfig.Handler = panicHandler(logTempFile)
		wrapConfig.Writer = io.MultiWriter(logTempFile, logWriter)
		wrapConfig.Stdout = outW
		exitStatus, err := panicwrap.Wrap(&wrapConfig)
		if err != nil {
			fmt.Fprintf(os.Stderr, "Couldn't start Terraform: %s", err)
			return 1
		}

		// If >= 0, we're the parent, so just exit
		if exitStatus >= 0 {
			return exitStatus
		}

		// We're the child, so just close the tempfile we made in order to
		// save file handles since the tempfile is only used by the parent.
		logTempFile.Close()
	}

	// Call the real main
	return wrappedMain()
}

func wrappedMain() int {
	log.SetOutput(os.Stderr)

	// Load the configuration
	config := BuiltinConfig

	// Make sure we clean up any managed plugins at the end of this
	defer plugin.CleanupClients()

	// Initialize the TFConfig settings for the commands...
	TFConfig.Providers = config.ProviderFactories()

	// Get the command line args. We shortcut "--version" and "-v" to
	// just show the version.
	args := os.Args[1:]
	for _, arg := range args {
		if arg == "-v" || arg == "--version" {
			newArgs := make([]string, len(args)+1)
			newArgs[0] = "version"
			copy(newArgs[1:], args)
			args = newArgs
			break
		}
	}

	cli := &cli.CLI{
		Args:     args,
		Commands: Commands,
		HelpFunc: cli.BasicHelpFunc("terraform"),
	}

	exitCode, err := cli.Run()
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error executing CLI: %s\n", err.Error())
		return 1
	}

	return exitCode
}

func copyOutput(r io.Reader) {
	pr, err := prefixedio.NewReader(r)
	if err != nil {
		panic(err)
	}

	stderrR, err := pr.Prefix(ErrorPrefix)
	if err != nil {
		panic(err)
	}
	stdoutR, err := pr.Prefix(OutputPrefix)
	if err != nil {
		panic(err)
	}

	go io.Copy(os.Stderr, stderrR)
	go io.Copy(os.Stdout, stdoutR)
}
<commit_msg>comments<commit_after>package main

import (
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"

	"github.com/hashicorp/terraform/plugin"
	"github.com/mitchellh/cli"
	"github.com/mitchellh/panicwrap"
	"github.com/mitchellh/prefixedio"
)

func main() {
	os.Exit(realMain())
}

func realMain() int {
	var wrapConfig panicwrap.WrapConfig

	if !panicwrap.Wrapped(&wrapConfig) {
		// Determine where logs should go in general (requested by the user)
		logWriter, err := logOutput()
		if err != nil {
			fmt.Fprintf(os.Stderr, "Couldn't setup log output: %s", err)
			return 1
		}
		if logWriter == nil {
			logWriter = ioutil.Discard
		}

		// We always send logs to a temporary file that we use in case
		// there is a panic. Otherwise, we delete it.
		logTempFile, err := ioutil.TempFile("", "terraform-log")
		if err != nil {
			fmt.Fprintf(os.Stderr, "Couldn't setup logging tempfile: %s", err)
			return 1
		}
		defer os.Remove(logTempFile.Name())
		defer logTempFile.Close()

		// Tell the logger to log to this file
		os.Setenv(EnvLog, "")
		os.Setenv(EnvLogFile, "")

		// Setup the prefixed readers that send data properly to
		// stdout/stderr.
		outR, outW := io.Pipe()
		go copyOutput(outR)

		// Create the configuration for panicwrap and wrap our executable
		wrapConfig.Handler = panicHandler(logTempFile)
		wrapConfig.Writer = io.MultiWriter(logTempFile, logWriter)
		wrapConfig.Stdout = outW
		exitStatus, err := panicwrap.Wrap(&wrapConfig)
		if err != nil {
			fmt.Fprintf(os.Stderr, "Couldn't start Terraform: %s", err)
			return 1
		}

		// If >= 0, we're the parent, so just exit
		if exitStatus >= 0 {
			return exitStatus
		}

		// We're the child, so just close the tempfile we made in order to
		// save file handles since the tempfile is only used by the parent.
		logTempFile.Close()
	}

	// Call the real main
	return wrappedMain()
}

func wrappedMain() int {
	log.SetOutput(os.Stderr)

	// Load the configuration
	config := BuiltinConfig

	// Make sure we clean up any managed plugins at the end of this
	defer plugin.CleanupClients()

	// Initialize the TFConfig settings for the commands...
	TFConfig.Providers = config.ProviderFactories()

	// Get the command line args. We shortcut "--version" and "-v" to
	// just show the version.
	args := os.Args[1:]
	for _, arg := range args {
		if arg == "-v" || arg == "--version" {
			newArgs := make([]string, len(args)+1)
			newArgs[0] = "version"
			copy(newArgs[1:], args)
			args = newArgs
			break
		}
	}

	cli := &cli.CLI{
		Args:     args,
		Commands: Commands,
		HelpFunc: cli.BasicHelpFunc("terraform"),
	}

	exitCode, err := cli.Run()
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error executing CLI: %s\n", err.Error())
		return 1
	}

	return exitCode
}

// copyOutput uses output prefixes to determine whether data on stdout
// should go to stdout or stderr. This is due to panicwrap using stderr
// as the log and error channel.
func copyOutput(r io.Reader) {
	pr, err := prefixedio.NewReader(r)
	if err != nil {
		panic(err)
	}

	stderrR, err := pr.Prefix(ErrorPrefix)
	if err != nil {
		panic(err)
	}
	stdoutR, err := pr.Prefix(OutputPrefix)
	if err != nil {
		panic(err)
	}

	go io.Copy(os.Stderr, stderrR)
	go io.Copy(os.Stdout, stdoutR)
}
<|endoftext|>


<commit_before>package main

import (
	"runtime"

	"fmt"

	"github.com/getfider/fider/app/models"
	"github.com/getfider/fider/app/pkg/env"
	_ "github.com/lib/pq"
	_ "github.com/mattes/migrate/database/postgres"
	_ "github.com/mattes/migrate/source/file"
)

var buildtime string
var version = "0.10.0-beta"

func main() {
	fmt.Printf("Application is starting...\n")
	fmt.Printf("GO_ENV: %s\n", env.Current())

	settings := &models.SystemSettings{
		BuildTime:       buildtime,
		Version:         version,
		Compiler:        runtime.Version(),
		Environment:     env.Current(),
		GoogleAnalytics: env.GetEnvOrDefault("GOOGLE_ANALYTICS", ""),
		Mode:            env.Mode(),
		Domain:          env.MultiTenantDomain(),
	}

	e := GetMainEngine(settings)
	e.Start(":" + env.GetEnvOrDefault("PORT", "3000"))
}
<commit_msg>update main.go version (#272)<commit_after>package main

import (
	"runtime"

	"fmt"

	"github.com/getfider/fider/app/models"
	"github.com/getfider/fider/app/pkg/env"
	_ "github.com/lib/pq"
	_ "github.com/mattes/migrate/database/postgres"
	_ "github.com/mattes/migrate/source/file"
)

var buildtime string
var version = "0.10.0"

func main() {
	fmt.Printf("Application is starting...\n")
	fmt.Printf("GO_ENV: %s\n", env.Current())

	settings := &models.SystemSettings{
		BuildTime:       buildtime,
		Version:         version,
		Compiler:        runtime.Version(),
		Environment:     env.Current(),
		GoogleAnalytics: env.GetEnvOrDefault("GOOGLE_ANALYTICS", ""),
		Mode:            env.Mode(),
		Domain:          env.MultiTenantDomain(),
	}

	e := GetMainEngine(settings)
	e.Start(":" + env.GetEnvOrDefault("PORT", "3000"))
}
<|endoftext|>


<commit_before>// msgp is a code generation tool for
// creating methods to serialize and de-serialize
// Go data structures to and from MessagePack.
//
// This package is targeted at the `go generate` tool.
// To use it, include the following directive in a
// go source file with types requiring source generation:
//
//     //go:generate msgp
//
// The go generate tool should set the proper environment variables for
// the generator to execute without any command-line flags. However, the
// following options are supported, if you need them:
//
//  -o = output file name (default is {input}_gen.go)
//  -file = input file name (or directory; default is $GOFILE, which is set by the `go generate` command)
//  -io = satisfy the `msgp.Decodable` and `msgp.Encodable` interfaces (default is true)
//  -marshal = satisfy the `msgp.Marshaler` and `msgp.Unmarshaler` interfaces (default is true)
//  -tests = generate tests and benchmarks (default is true)
//
// For more information, please read README.md, and the wiki at github.com/tinylib/msgp
//
package main

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

	"github.com/tinylib/msgp/gen"
	"github.com/tinylib/msgp/parse"
	"github.com/tinylib/msgp/printer"
	"github.com/ttacon/chalk"
)

var (
	out     = flag.String("o", "", "output file")
	file    = flag.String("file", "", "input file")
	encode  = flag.Bool("io", true, "create Encode and Decode methods")
	marshal = flag.Bool("marshal", true, "create Marshal and Unmarshal methods")
	tests   = flag.Bool("tests", true, "create tests and benchmarks")
)

func main() {
	flag.Parse()

	// GOFILE is set by go generate
	if *file == "" {
		*file = os.Getenv("GOFILE")
		if *file == "" {
			fmt.Println(chalk.Red.Color("No file to parse."))
			os.Exit(1)
		}
	}

	var mode gen.Method
	if *encode {
		mode |= (gen.Encode | gen.Decode | gen.Size)
	}
	if *marshal {
		mode |= (gen.Marshal | gen.Unmarshal)
	}
	if *tests {
		mode |= gen.Test
	}

	if mode&^gen.Test == 0 {
		fmt.Println(chalk.Red.Color("No methods to generate; -io=false && -marshal=false"))
		os.Exit(1)
	}

	if err := Run(*file, mode); err != nil {
		fmt.Println(chalk.Red.Color(err.Error()))
		os.Exit(1)
	}
}

// Run writes all methods using the associated file or path, e.g.
//
//	err := msgp.Run("path/to/myfile.go", gen.Size|gen.Marshal|gen.Unmarshal|gen.Test)
//
func Run(gofile string, mode gen.Method) error {
	if mode&^gen.Test == 0 {
		return nil
	}
	fmt.Println(chalk.Magenta.Color("======== MessagePack Code Generator ======="))
	fmt.Printf(chalk.Magenta.Color(">>> Input: \"%s\"...\n"), gofile)
	fs, err := parse.File(gofile)
	if err != nil {
		return err
	}

	if len(fs.Identities) == 0 {
		fmt.Println(chalk.Magenta.Color("No types requiring code generation were found!"))
		return nil
	}

	return printer.PrintFile(newFilename(gofile, fs.Package), fs, mode)
}

// picks a new file name based on input flags and input filename(s).
func newFilename(old string, pkg string) string {
	if *out != "" {
		if pre := strings.TrimPrefix(*out, old); len(pre) > 0 &&
			!strings.HasSuffix(*out, ".go") {
			return filepath.Join(old, *out)
		}
		return *out
	}

	if fi, err := os.Stat(old); err == nil && fi.IsDir() {
		old = filepath.Join(old, pkg)
	}
	// new file name is old file name + _gen.go
	return strings.TrimSuffix(old, ".go") + "_gen.go"
}
<commit_msg>Always generate msgp.Sizer if -marshal is true.<commit_after>// msgp is a code generation tool for
// creating methods to serialize and de-serialize
// Go data structures to and from MessagePack.
//
// This package is targeted at the `go generate` tool.
// To use it, include the following directive in a
// go source file with types requiring source generation:
//
//     //go:generate msgp
//
// The go generate tool should set the proper environment variables for
// the generator to execute without any command-line flags. However, the
// following options are supported, if you need them:
//
//  -o = output file name (default is {input}_gen.go)
//  -file = input file name (or directory; default is $GOFILE, which is set by the `go generate` command)
//  -io = satisfy the `msgp.Decodable` and `msgp.Encodable` interfaces (default is true)
//  -marshal = satisfy the `msgp.Marshaler` and `msgp.Unmarshaler` interfaces (default is true)
//  -tests = generate tests and benchmarks (default is true)
//
// For more information, please read README.md, and the wiki at github.com/tinylib/msgp
//
package main

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

	"github.com/tinylib/msgp/gen"
	"github.com/tinylib/msgp/parse"
	"github.com/tinylib/msgp/printer"
	"github.com/ttacon/chalk"
)

var (
	out     = flag.String("o", "", "output file")
	file    = flag.String("file", "", "input file")
	encode  = flag.Bool("io", true, "create Encode and Decode methods")
	marshal = flag.Bool("marshal", true, "create Marshal and Unmarshal methods")
	tests   = flag.Bool("tests", true, "create tests and benchmarks")
)

func main() {
	flag.Parse()

	// GOFILE is set by go generate
	if *file == "" {
		*file = os.Getenv("GOFILE")
		if *file == "" {
			fmt.Println(chalk.Red.Color("No file to parse."))
			os.Exit(1)
		}
	}

	var mode gen.Method
	if *encode {
		mode |= (gen.Encode | gen.Decode | gen.Size)
	}
	if *marshal {
		mode |= (gen.Marshal | gen.Unmarshal | gen.Size)
	}
	if *tests {
		mode |= gen.Test
	}

	if mode&^gen.Test == 0 {
		fmt.Println(chalk.Red.Color("No methods to generate; -io=false && -marshal=false"))
		os.Exit(1)
	}

	if err := Run(*file, mode); err != nil {
		fmt.Println(chalk.Red.Color(err.Error()))
		os.Exit(1)
	}
}

// Run writes all methods using the associated file or path, e.g.
//
//	err := msgp.Run("path/to/myfile.go", gen.Size|gen.Marshal|gen.Unmarshal|gen.Test)
//
func Run(gofile string, mode gen.Method) error {
	if mode&^gen.Test == 0 {
		return nil
	}
	fmt.Println(chalk.Magenta.Color("======== MessagePack Code Generator ======="))
	fmt.Printf(chalk.Magenta.Color(">>> Input: \"%s\"...\n"), gofile)
	fs, err := parse.File(gofile)
	if err != nil {
		return err
	}

	if len(fs.Identities) == 0 {
		fmt.Println(chalk.Magenta.Color("No types requiring code generation were found!"))
		return nil
	}

	return printer.PrintFile(newFilename(gofile, fs.Package), fs, mode)
}

// picks a new file name based on input flags and input filename(s).
func newFilename(old string, pkg string) string {
	if *out != "" {
		if pre := strings.TrimPrefix(*out, old); len(pre) > 0 &&
			!strings.HasSuffix(*out, ".go") {
			return filepath.Join(old, *out)
		}
		return *out
	}

	if fi, err := os.Stat(old); err == nil && fi.IsDir() {
		old = filepath.Join(old, pkg)
	}
	// new file name is old file name + _gen.go
	return strings.TrimSuffix(old, ".go") + "_gen.go"
}
<|endoftext|>


<commit_before>// Copyright (C) 2014 Jakob Borg and Contributors (see the CONTRIBUTORS file).

package main

import (
	"bytes"
	"encoding/binary"
	"flag"
	"fmt"
	"io"
	"log"
	"net"
	"os"
	"path/filepath"
	"sync"
	"time"

	"github.com/golang/groupcache/lru"
	"github.com/juju/ratelimit"
	"github.com/syncthing/protocol"
	"github.com/syncthing/syncthing/internal/discover"
	"github.com/syndtr/goleveldb/leveldb"
	"github.com/syndtr/goleveldb/leveldb/opt"
)

const cacheLimitSeconds = 3600

var (
	lock       sync.Mutex
	queries    = 0
	announces  = 0
	answered   = 0
	limited    = 0
	unknowns   = 0
	debug      = false
	lruSize    = 1024
	limitAvg   = 1
	limitBurst = 10
	limiter    *lru.Cache
)

func main() {
	var listen string
	var timestamp bool
	var statsIntv int
	var statsFile string
	var unknownFile string
	var dbDir string

	flag.StringVar(&listen, "listen", ":22026", "Listen address")
	flag.BoolVar(&debug, "debug", false, "Enable debug output")
	flag.BoolVar(&timestamp, "timestamp", true, "Timestamp the log output")
	flag.IntVar(&statsIntv, "stats-intv", 0, "Statistics output interval (s)")
	flag.StringVar(&statsFile, "stats-file", "/var/discosrv/stats", "Statistics file name")
	flag.StringVar(&unknownFile, "unknown-file", "", "Unknown packet log file name")
	flag.IntVar(&lruSize, "limit-cache", lruSize, "Limiter cache entries")
	flag.IntVar(&limitAvg, "limit-avg", limitAvg, "Allowed average package rate, per 10 s")
	flag.IntVar(&limitBurst, "limit-burst", limitBurst, "Allowed burst size, packets")
	flag.StringVar(&dbDir, "db-dir", "/var/discosrv/db", "Database directory")
	flag.Parse()

	limiter = lru.New(lruSize)

	log.SetOutput(os.Stdout)
	if !timestamp {
		log.SetFlags(0)
	}

	addr, _ := net.ResolveUDPAddr("udp", listen)
	conn, err := net.ListenUDP("udp", addr)
	if err != nil {
		log.Fatal(err)
	}

	parentDir := filepath.Dir(dbDir)
	if _, err := os.Stat(parentDir); err != nil && os.IsNotExist(err) {
		err = os.MkdirAll(parentDir, 0755)
		if err != nil {
			log.Fatal(err)
		}
	}

	db, err := leveldb.OpenFile(dbDir, &opt.Options{OpenFilesCacheCapacity: 32})
	if err != nil {
		log.Fatal(err)
	}

	statsLog, err := os.OpenFile(statsFile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
	if err != nil {
		log.Fatal(err)
	}

	var unknownLog io.Writer
	if unknownFile != "" {
		unknownLog, err = os.OpenFile(unknownFile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
		if err != nil {
			log.Fatal(err)
		}
	}

	if statsIntv > 0 {
		go logStats(statsLog, statsIntv)
	}

	go clean(statsLog, db)

	var buf = make([]byte, 1024)
	for {
		buf = buf[:cap(buf)]
		n, addr, err := conn.ReadFromUDP(buf)

		if limit(addr) {
			// Rate limit in effect for source
			continue
		}

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

		if n < 4 {
			log.Printf("Received short packet (%d bytes)", n)
			continue
		}

		buf = buf[:n]
		magic := binary.BigEndian.Uint32(buf)

		switch magic {
		case discover.AnnouncementMagic:
			err := handleAnnounceV2(db, addr, buf)
			if err != nil && unknownLog != nil {
				fmt.Fprintf(unknownLog, "AE %d %v %x\n", time.Now().Unix(), addr, buf)
			}

		case discover.QueryMagic:
			err := handleQueryV2(db, conn, addr, buf)
			if err != nil && unknownLog != nil {
				fmt.Fprintf(unknownLog, "QE %d %v %x\n", time.Now().Unix(), addr, buf)
			}

		default:
			lock.Lock()
			unknowns++
			lock.Unlock()
			if unknownLog != nil {
				fmt.Fprintf(unknownLog, "UN %d %v %x\n", time.Now().Unix(), addr, buf)
			}
		}
	}
}

func limit(addr *net.UDPAddr) bool {
	key := addr.IP.String()

	lock.Lock()
	defer lock.Unlock()

	bkt, ok := limiter.Get(key)
	if ok {
		bkt := bkt.(*ratelimit.Bucket)
		if bkt.TakeAvailable(1) != 1 {
			// Rate limit exceeded; ignore packet
			if debug {
				log.Println("Rate limit exceeded for", key)
			}
			limited++
			return true
		}
	} else {
		if debug {
			log.Println("New limiter for", key)
		}
		// One packet per ten seconds average rate, burst ten packets
		limiter.Add(key, ratelimit.NewBucket(10*time.Second/time.Duration(limitAvg), int64(limitBurst)))
	}

	return false
}

func handleAnnounceV2(db *leveldb.DB, addr *net.UDPAddr, buf []byte) error {
	var pkt discover.Announce
	err := pkt.UnmarshalXDR(buf)
	if err != nil && err != io.EOF {
		return err
	}
	if debug {
		log.Printf("<- %v %#v", addr, pkt)
	}

	lock.Lock()
	announces++
	lock.Unlock()

	ip := addr.IP.To4()
	if ip == nil {
		ip = addr.IP.To16()
	}

	var addrs []address
	now := time.Now().Unix()
	for _, addr := range pkt.This.Addresses {
		tip := addr.IP
		if len(tip) == 0 {
			tip = ip
		}
		addrs = append(addrs, address{
			ip:   tip,
			port: addr.Port,
			seen: now,
		})
	}

	var id protocol.DeviceID
	if len(pkt.This.ID) == 32 {
		// Raw node ID
		copy(id[:], pkt.This.ID)
	} else {
		err = id.UnmarshalText(pkt.This.ID)
		if err != nil {
			return err
		}
	}

	update(db, id, addrs)
	return nil
}

func handleQueryV2(db *leveldb.DB, conn *net.UDPConn, addr *net.UDPAddr, buf []byte) error {
	var pkt discover.Query
	err := pkt.UnmarshalXDR(buf)
	if err != nil {
		return err
	}
	if debug {
		log.Printf("<- %v %#v", addr, pkt)
	}

	var id protocol.DeviceID
	if len(pkt.DeviceID) == 32 {
		// Raw node ID
		copy(id[:], pkt.DeviceID)
	} else {
		err = id.UnmarshalText(pkt.DeviceID)
		if err != nil {
			return err
		}
	}

	lock.Lock()
	queries++
	lock.Unlock()

	addrs := get(db, id)

	now := time.Now().Unix()
	if len(addrs) > 0 {
		ann := discover.Announce{
			Magic: discover.AnnouncementMagic,
			This: discover.Device{
				ID: pkt.DeviceID,
			},
		}
		for _, addr := range addrs {
			if now-addr.seen > cacheLimitSeconds {
				continue
			}
			ann.This.Addresses = append(ann.This.Addresses, discover.Address{IP: addr.ip, Port: addr.port})
		}
		if debug {
			log.Printf("-> %v %#v", addr, pkt)
		}

		if len(ann.This.Addresses) == 0 {
			return nil
		}

		tb, err := ann.MarshalXDR()
		if err != nil {
			log.Println("QueryV2 response marshal:", err)
			return nil
		}
		_, err = conn.WriteToUDP(tb, addr)
		if err != nil {
			log.Println("QueryV2 response write:", err)
			return nil
		}

		lock.Lock()
		answered++
		lock.Unlock()
	}
	return nil
}

func next(intv int) time.Time {
	d := time.Duration(intv) * time.Second
	t0 := time.Now()
	t1 := t0.Add(d).Truncate(d)
	time.Sleep(t1.Sub(t0))
	return t1
}

func logStats(statsLog io.Writer, intv int) {
	for {
		t := next(intv)

		lock.Lock()

		fmt.Fprintf(statsLog, "%d Queries:%d Answered:%d Announces:%d Unknown:%d Limited:%d\n",
			t.Unix(), queries, answered, announces, unknowns, limited)

		queries = 0
		announces = 0
		answered = 0
		limited = 0
		unknowns = 0

		lock.Unlock()
	}
}

func get(db *leveldb.DB, id protocol.DeviceID) []address {
	var addrs addressList
	val, err := db.Get(id[:], nil)
	if err == nil {
		addrs.UnmarshalXDR(val)
	}
	return addrs.addresses
}

func update(db *leveldb.DB, id protocol.DeviceID, addrs []address) {
	var newAddrs addressList

	val, err := db.Get(id[:], nil)
	if err == nil {
		newAddrs.UnmarshalXDR(val)
	}

nextAddr:
	for _, newAddr := range addrs {
		for i, exAddr := range newAddrs.addresses {
			if bytes.Compare(newAddr.ip, exAddr.ip) == 0 {
				newAddrs.addresses[i] = newAddr
				continue nextAddr
			}
		}
		newAddrs.addresses = append(newAddrs.addresses, newAddr)
	}

	db.Put(id[:], newAddrs.MarshalXDR(), nil)
}

func clean(statsLog io.Writer, db *leveldb.DB) {
	for {
		now := next(cacheLimitSeconds)
		nowSecs := now.Unix()

		var kept, deleted int64
		iter := db.NewIterator(nil, nil)
		for iter.Next() {
			var addrs addressList
			addrs.UnmarshalXDR(iter.Value())

			// Remove expired addresses
			newAddrs := addrs.addresses
			for i := 0; i < len(newAddrs); i++ {
				if nowSecs-newAddrs[i].seen > cacheLimitSeconds {
					newAddrs[i] = newAddrs[len(newAddrs)-1]
					newAddrs = newAddrs[:len(newAddrs)-1]
				}
			}

			// Delete empty records
			if len(newAddrs) == 0 {
				db.Delete(iter.Key(), nil)
				deleted++
				continue
			}

			// Update changed records
			if len(newAddrs) != len(addrs.addresses) {
				addrs.addresses = newAddrs
				db.Put(iter.Key(), addrs.MarshalXDR(), nil)
			}
			kept++
		}
		iter.Release()

		fmt.Fprintf(statsLog, "%d Kept:%d Deleted:%d Took:%0.04fs\n", nowSecs, kept, deleted, time.Since(now).Seconds())
	}
}
<commit_msg>s/internal/lib/<commit_after>// Copyright (C) 2014 Jakob Borg and Contributors (see the CONTRIBUTORS file).

package main

import (
	"bytes"
	"encoding/binary"
	"flag"
	"fmt"
	"io"
	"log"
	"net"
	"os"
	"path/filepath"
	"sync"
	"time"

	"github.com/golang/groupcache/lru"
	"github.com/juju/ratelimit"
	"github.com/syncthing/protocol"
	"github.com/syncthing/syncthing/lib/discover"
	"github.com/syndtr/goleveldb/leveldb"
	"github.com/syndtr/goleveldb/leveldb/opt"
)

const cacheLimitSeconds = 3600

var (
	lock       sync.Mutex
	queries    = 0
	announces  = 0
	answered   = 0
	limited    = 0
	unknowns   = 0
	debug      = false
	lruSize    = 1024
	limitAvg   = 1
	limitBurst = 10
	limiter    *lru.Cache
)

func main() {
	var listen string
	var timestamp bool
	var statsIntv int
	var statsFile string
	var unknownFile string
	var dbDir string

	flag.StringVar(&listen, "listen", ":22026", "Listen address")
	flag.BoolVar(&debug, "debug", false, "Enable debug output")
	flag.BoolVar(&timestamp, "timestamp", true, "Timestamp the log output")
	flag.IntVar(&statsIntv, "stats-intv", 0, "Statistics output interval (s)")
	flag.StringVar(&statsFile, "stats-file", "/var/discosrv/stats", "Statistics file name")
	flag.StringVar(&unknownFile, "unknown-file", "", "Unknown packet log file name")
	flag.IntVar(&lruSize, "limit-cache", lruSize, "Limiter cache entries")
	flag.IntVar(&limitAvg, "limit-avg", limitAvg, "Allowed average package rate, per 10 s")
	flag.IntVar(&limitBurst, "limit-burst", limitBurst, "Allowed burst size, packets")
	flag.StringVar(&dbDir, "db-dir", "/var/discosrv/db", "Database directory")
	flag.Parse()

	limiter = lru.New(lruSize)

	log.SetOutput(os.Stdout)
	if !timestamp {
		log.SetFlags(0)
	}

	addr, _ := net.ResolveUDPAddr("udp", listen)
	conn, err := net.ListenUDP("udp", addr)
	if err != nil {
		log.Fatal(err)
	}

	parentDir := filepath.Dir(dbDir)
	if _, err := os.Stat(parentDir); err != nil && os.IsNotExist(err) {
		err = os.MkdirAll(parentDir, 0755)
		if err != nil {
			log.Fatal(err)
		}
	}

	db, err := leveldb.OpenFile(dbDir, &opt.Options{OpenFilesCacheCapacity: 32})
	if err != nil {
		log.Fatal(err)
	}

	statsLog, err := os.OpenFile(statsFile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
	if err != nil {
		log.Fatal(err)
	}

	var unknownLog io.Writer
	if unknownFile != "" {
		unknownLog, err = os.OpenFile(unknownFile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
		if err != nil {
			log.Fatal(err)
		}
	}

	if statsIntv > 0 {
		go logStats(statsLog, statsIntv)
	}

	go clean(statsLog, db)

	var buf = make([]byte, 1024)
	for {
		buf = buf[:cap(buf)]
		n, addr, err := conn.ReadFromUDP(buf)

		if limit(addr) {
			// Rate limit in effect for source
			continue
		}

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

		if n < 4 {
			log.Printf("Received short packet (%d bytes)", n)
			continue
		}

		buf = buf[:n]
		magic := binary.BigEndian.Uint32(buf)

		switch magic {
		case discover.AnnouncementMagic:
			err := handleAnnounceV2(db, addr, buf)
			if err != nil && unknownLog != nil {
				fmt.Fprintf(unknownLog, "AE %d %v %x\n", time.Now().Unix(), addr, buf)
			}

		case discover.QueryMagic:
			err := handleQueryV2(db, conn, addr, buf)
			if err != nil && unknownLog != nil {
				fmt.Fprintf(unknownLog, "QE %d %v %x\n", time.Now().Unix(), addr, buf)
			}

		default:
			lock.Lock()
			unknowns++
			lock.Unlock()
			if unknownLog != nil {
				fmt.Fprintf(unknownLog, "UN %d %v %x\n", time.Now().Unix(), addr, buf)
			}
		}
	}
}

func limit(addr *net.UDPAddr) bool {
	key := addr.IP.String()

	lock.Lock()
	defer lock.Unlock()

	bkt, ok := limiter.Get(key)
	if ok {
		bkt := bkt.(*ratelimit.Bucket)
		if bkt.TakeAvailable(1) != 1 {
			// Rate limit exceeded; ignore packet
			if debug {
				log.Println("Rate limit exceeded for", key)
			}
			limited++
			return true
		}
	} else {
		if debug {
			log.Println("New limiter for", key)
		}
		// One packet per ten seconds average rate, burst ten packets
		limiter.Add(key, ratelimit.NewBucket(10*time.Second/time.Duration(limitAvg), int64(limitBurst)))
	}

	return false
}

func handleAnnounceV2(db *leveldb.DB, addr *net.UDPAddr, buf []byte) error {
	var pkt discover.Announce
	err := pkt.UnmarshalXDR(buf)
	if err != nil && err != io.EOF {
		return err
	}
	if debug {
		log.Printf("<- %v %#v", addr, pkt)
	}

	lock.Lock()
	announces++
	lock.Unlock()

	ip := addr.IP.To4()
	if ip == nil {
		ip = addr.IP.To16()
	}

	var addrs []address
	now := time.Now().Unix()
	for _, addr := range pkt.This.Addresses {
		tip := addr.IP
		if len(tip) == 0 {
			tip = ip
		}
		addrs = append(addrs, address{
			ip:   tip,
			port: addr.Port,
			seen: now,
		})
	}

	var id protocol.DeviceID
	if len(pkt.This.ID) == 32 {
		// Raw node ID
		copy(id[:], pkt.This.ID)
	} else {
		err = id.UnmarshalText(pkt.This.ID)
		if err != nil {
			return err
		}
	}

	update(db, id, addrs)
	return nil
}

func handleQueryV2(db *leveldb.DB, conn *net.UDPConn, addr *net.UDPAddr, buf []byte) error {
	var pkt discover.Query
	err := pkt.UnmarshalXDR(buf)
	if err != nil {
		return err
	}
	if debug {
		log.Printf("<- %v %#v", addr, pkt)
	}

	var id protocol.DeviceID
	if len(pkt.DeviceID) == 32 {
		// Raw node ID
		copy(id[:], pkt.DeviceID)
	} else {
		err = id.UnmarshalText(pkt.DeviceID)
		if err != nil {
			return err
		}
	}

	lock.Lock()
	queries++
	lock.Unlock()

	addrs := get(db, id)

	now := time.Now().Unix()
	if len(addrs) > 0 {
		ann := discover.Announce{
			Magic: discover.AnnouncementMagic,
			This: discover.Device{
				ID: pkt.DeviceID,
			},
		}
		for _, addr := range addrs {
			if now-addr.seen > cacheLimitSeconds {
				continue
			}
			ann.This.Addresses = append(ann.This.Addresses, discover.Address{IP: addr.ip, Port: addr.port})
		}
		if debug {
			log.Printf("-> %v %#v", addr, pkt)
		}

		if len(ann.This.Addresses) == 0 {
			return nil
		}

		tb, err := ann.MarshalXDR()
		if err != nil {
			log.Println("QueryV2 response marshal:", err)
			return nil
		}
		_, err = conn.WriteToUDP(tb, addr)
		if err != nil {
			log.Println("QueryV2 response write:", err)
			return nil
		}

		lock.Lock()
		answered++
		lock.Unlock()
	}
	return nil
}

func next(intv int) time.Time {
	d := time.Duration(intv) * time.Second
	t0 := time.Now()
	t1 := t0.Add(d).Truncate(d)
	time.Sleep(t1.Sub(t0))
	return t1
}

func logStats(statsLog io.Writer, intv int) {
	for {
		t := next(intv)

		lock.Lock()

		fmt.Fprintf(statsLog, "%d Queries:%d Answered:%d Announces:%d Unknown:%d Limited:%d\n",
			t.Unix(), queries, answered, announces, unknowns, limited)

		queries = 0
		announces = 0
		answered = 0
		limited = 0
		unknowns = 0

		lock.Unlock()
	}
}

func get(db *leveldb.DB, id protocol.DeviceID) []address {
	var addrs addressList
	val, err := db.Get(id[:], nil)
	if err == nil {
		addrs.UnmarshalXDR(val)
	}
	return addrs.addresses
}

func update(db *leveldb.DB, id protocol.DeviceID, addrs []address) {
	var newAddrs addressList

	val, err := db.Get(id[:], nil)
	if err == nil {
		newAddrs.UnmarshalXDR(val)
	}

nextAddr:
	for _, newAddr := range addrs {
		for i, exAddr := range newAddrs.addresses {
			if bytes.Compare(newAddr.ip, exAddr.ip) == 0 {
				newAddrs.addresses[i] = newAddr
				continue nextAddr
			}
		}
		newAddrs.addresses = append(newAddrs.addresses, newAddr)
	}

	db.Put(id[:], newAddrs.MarshalXDR(), nil)
}

func clean(statsLog io.Writer, db *leveldb.DB) {
	for {
		now := next(cacheLimitSeconds)
		nowSecs := now.Unix()

		var kept, deleted int64
		iter := db.NewIterator(nil, nil)
		for iter.Next() {
			var addrs addressList
			addrs.UnmarshalXDR(iter.Value())

			// Remove expired addresses
			newAddrs := addrs.addresses
			for i := 0; i < len(newAddrs); i++ {
				if nowSecs-newAddrs[i].seen > cacheLimitSeconds {
					newAddrs[i] = newAddrs[len(newAddrs)-1]
					newAddrs = newAddrs[:len(newAddrs)-1]
				}
			}

			// Delete empty records
			if len(newAddrs) == 0 {
				db.Delete(iter.Key(), nil)
				deleted++
				continue
			}

			// Update changed records
			if len(newAddrs) != len(addrs.addresses) {
				addrs.addresses = newAddrs
				db.Put(iter.Key(), addrs.MarshalXDR(), nil)
			}
			kept++
		}
		iter.Release()

		fmt.Fprintf(statsLog, "%d Kept:%d Deleted:%d Took:%0.04fs\n", nowSecs, kept, deleted, time.Since(now).Seconds())
	}
}
<|endoftext|>