<commit_before>package main

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

	"github.com/inconshreveable/mousetrap"

	flag "github.com/jessevdk/go-flags"
)

//Default global variables
var (
	elog   *log.Logger //The logger for errors
	done   chan bool
	prefix = "https:"
)

func init() {
	if mousetrap.StartedByExplorer() {
		fmt.Println("Don't double-click ponydownloader")
		fmt.Println("You need to open cmd.exe and run it from the command line!")
		time.Sleep(5 * time.Second)
		os.Exit(1)
	}
	done = make(chan bool)
}

func main() {
	fmt.Println("Derpiboo.ru Downloader version 0.5.0")

	err := flag.IniParse("config.ini", &opts)
	if err != nil {
		switch err.(type) {
		default:
			panic(err)
		case *os.PathError:
			fmt.Println("config.ini not found, using defaults")
		}
	}
	var iniopts = opts

	args, err := flag.Parse(&opts)
	if err != nil {
		flagError := err.(*flag.Error)

		switch flagError.Type {
		case flag.ErrHelp:
		case flag.ErrUnknownFlag:
			fmt.Println("Use --help to view all available options")
		default:
			fmt.Printf("Error parsing flags: %s\n", err)
		}
		return
	}

	elog = SetLog() //Setting up logging of errors

	WriteConfig(iniopts)

	if len(args) != 0 {
		elog.Println("Too many arguments, skipping following:", args)

	}

	if len(opts.Args.IDs) == 0 && opts.Tag == "" { //If no arguments after flags and empty/unchanged tag, what we should download? Sane end of line.

		log.SetPrefix("Done at ")                //We can not do this with elog!
		log.Println("Nothing to download, bye!") //Need to reshuffle flow: now it could end before it starts.
		os.Exit(0)
	}

	if opts.NoHTTPS {
		prefix = "http:"
	}

	//Creating directory for downloads if it does not yet exist
	err = os.MkdirAll(opts.ImageDir, 0755)

	if err != nil { //Execute bit means different thing for directories that for files. And I was stupid.
		elog.Fatalln(err) //We can not create folder for images, end of line.
	}

	//	Creating channels to pass info to downloader and to signal job well done
	imgdat := make(ImageCh, opts.QDepth) //Better leave default queue depth. Experiment shown that depth about 20 provides optimal perfomance on my system

	if opts.Tag == "" { //Because we can put imgid with flags. Why not?

		log.Println("Processing image No", opts.Args.IDs)
		go imgdat.ParseImg() // Sending imgid to parser. Here validity is our problem

	} else {

		//	and here we send tags to getter/parser. Validity is server problem, mostly

		log.Println("Processing tags", opts.Tag)
		go imgdat.ParseTag()
	}

	log.Println("Starting worker") //It would be funny if worker goroutine does not start

	filterInit(opts)
	filtimgdat := FilterChannel(imgdat) //see to move it into filter.Filter(inchan, outchan) where all filtration is done

	go filtimgdat.DlImg()

	<-done
	log.SetPrefix("Done at ")
	log.Println("Finished")
	//And we are done here! Hooray!
	return
}
<commit_msg>Refactor: abstracting complexity away<commit_after>package main

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

	"github.com/inconshreveable/mousetrap"

	flag "github.com/jessevdk/go-flags"
)

//Default global variables
var (
	elog   *log.Logger //The logger for errors
	done   chan bool
	prefix = "https:"
)

func init() {
	if mousetrap.StartedByExplorer() {
		fmt.Println("Don't double-click ponydownloader")
		fmt.Println("You need to open cmd.exe and run it from the command line!")
		time.Sleep(5 * time.Second)
		os.Exit(1)
	}
	done = make(chan bool)
}

func main() {
	fmt.Println("Derpiboo.ru Downloader version 0.5.0")

	args, iniopts := configSetup(&opts)

	elog = SetLog() //Setting up logging of errors

	WriteConfig(iniopts)

	if len(args) != 0 {
		elog.Println("Too many arguments, skipping following:", args)

	}

	if len(opts.Args.IDs) == 0 && opts.Tag == "" { //If no arguments after flags and empty/unchanged tag, what we should download? Sane end of line.

		log.SetPrefix("Done at ")                //We can not do this with elog!
		log.Println("Nothing to download, bye!") //Need to reshuffle flow: now it could end before it starts.
		os.Exit(0)
	}

	if opts.NoHTTPS {
		prefix = "http:"
	}

	//Creating directory for downloads if it does not yet exist
	err := os.MkdirAll(opts.ImageDir, 0755)

	if err != nil { //Execute bit means different thing for directories that for files. And I was stupid.
		elog.Fatalln(err) //We can not create folder for images, end of line.
	}

	//	Creating channels to pass info to downloader and to signal job well done
	imgdat := make(ImageCh, opts.QDepth) //Better leave default queue depth. Experiment shown that depth about 20 provides optimal perfomance on my system

	if opts.Tag == "" { //Because we can put imgid with flags. Why not?

		log.Println("Processing image No", opts.Args.IDs)
		go imgdat.ParseImg() // Sending imgid to parser. Here validity is our problem

	} else {

		//	and here we send tags to getter/parser. Validity is server problem, mostly

		log.Println("Processing tags", opts.Tag)
		go imgdat.ParseTag()
	}

	log.Println("Starting worker") //It would be funny if worker goroutine does not start

	filterInit(opts)
	filtimgdat := FilterChannel(imgdat) //see to move it into filter.Filter(inchan, outchan) where all filtration is done

	go filtimgdat.DlImg()

	<-done
	log.SetPrefix("Done at ")
	log.Println("Finished")
	//And we are done here! Hooray!
	return
}

func configSetup(*Options) ([]string, Options) {
	err := flag.IniParse("config.ini", &opts)
	if err != nil {
		switch err.(type) {
		default:
			panic(err)
		case *os.PathError:
			fmt.Println("config.ini not found, using defaults")
		}
	}
	var iniopts = opts

	args, err := flag.Parse(&opts)
	if err != nil {
		flagError := err.(*flag.Error)

		switch flagError.Type {
		case flag.ErrHelp:
		case flag.ErrUnknownFlag:
			fmt.Println("Use --help to view all available options")
			os.Exit(0)
		default:
			fmt.Printf("Error parsing flags: %s\n", err)
			os.Exit(1)
		}
	}
	return args, iniopts
}
<|endoftext|>


<commit_before>// Wago (Watch, Go)
// A general purpose watch / build development tool.

// TODO: catch SIGINT and reset term
// see https://askubuntu.com/questions/171449/shell-does-not-show-typed-in-commands-reset-works-but-what-happened

package main

import (
	"flag"
	"fmt"
	"github.com/JonahBraun/dog"
	"github.com/howeyc/fsnotify"
	"net/http"
	"os"
	"path/filepath"
	"regexp"
	"runtime"
	"sync"
)

var (
	log     = dog.NewDog(dog.DEBUG)
	verbose = flag.Bool("v", false, "Verbose")
	quiet   = flag.Bool("q", false, "Quiet, only warnings and errors")

	buildCmd      = flag.String("cmd", "", "Run command, wait for it to complete.")
	daemonCmd     = flag.String("daemon", "", "Run command and leave running in the background.")
	daemonTimer   = flag.Int("timer", 0, "Wait miliseconds after starting daemon, then continue.")
	daemonTrigger = flag.String("trigger", "", "Wait for daemon to output this string, then continue.")
	exitWait      = flag.Int("exitwait", 50, "Max miliseconds a process has after a SIGTERM to exit before a SIGKILL.")
	fiddle        = flag.Bool("fiddle", false, "CLI fiddle mode! Start a web server, open browser to URL of targetDir/index.html")
	postCmd       = flag.String("pcmd", "", "Run command after daemon starts. Use this to kick off your test suite.")
	recursive     = flag.Bool("recursive", true, "Watch directory tree recursively.")
	targetDir     = flag.String("dir", "", "Directory to watch, defaults to current.")
	url           = flag.String("url", "", "Open browser to this URL after all commands are successful.")
	watchRegex    = flag.String("watch", `/\w[\w\.]*": (CREATE|MODIFY)`, "React to FS events matching regex. Use -v to see all events.")
	ignoreRegex   = flag.String("ignore", `\.(git|hg|svn)`, "Ignore directories matching regex.")
	webServer     = flag.String("web", "", "Start a web server at this address, e.g. :8420")
	shell         = flag.String("shell", "", "Shell used to run commands, defaults to $SHELL, fallback to /bin/sh")
)

type Watcher struct {
	Event chan fmt.Stringer
	Error chan error
}

func main() {
	// the following function calls merely serve to logically organize what
	// is otherwise a VERY lengthy setup

	// TODO: have configSetup return a config object so that the reliance on
	// config globals is removed
	configSetup()

	startWebServer()

	runChain(newWatcher(), make(chan struct{}))
}

func runChain(watcher *Watcher, quit chan struct{}) {
	chain := make([]Runnable, 0, 5)

	// build chain of runnables
	if len(*buildCmd) > 0 {
		chain = append(chain, NewRunWait(*buildCmd))
	}
	if len(*daemonCmd) > 0 {
		if len(*daemonTrigger) > 0 {
			chain = append(chain, NewDaemonTrigger(*daemonCmd, *daemonTrigger))
		} else {
			chain = append(chain, NewDaemonTimer(*daemonCmd, *daemonTimer))
		}
	}
	if len(*postCmd) > 0 {
		chain = append(chain, NewRunWait(*postCmd))
	}
	if *url != "" {
		chain = append(chain, NewBrowser(*url))
	}

	eventRegex, err := regexp.Compile(*watchRegex)
	if err != nil {
		log.Fatal("Watch regex compile error:", err)(1)
	}

	var wg sync.WaitGroup

	// main loop
	for {
		// all channels of struct{} are disposable, single use
		// kill is passed to all Runnable so they know when they should exit
		kill := make(chan struct{})

		var drain func()
		drain = func() {
			select {
			case ev := <-watcher.Event:
				log.Debug("Extra event ignored:", ev.String())
				drain()
			default:
			}
		}
		drain()

		// event loop
		go func() {
			for {
				select {
				case ev := <-watcher.Event:
					if eventRegex.MatchString(ev.String()) {
						log.Info("Matched event:", ev.String())
						close(kill)
						return
					} else {
						log.Debug("Ignored event:", ev.String())
					}
				case err = <-watcher.Error:
					log.Fatal("Watcher error:", err)(5)
				case <-quit:
					// currently only used by test suite
					close(kill)
					return
				}
			}
		}()

	RunLoop:
		for _, runnable := range chain {
			done, dead := runnable(kill)
			wg.Add(1)

			go func() {
				wg.Done()
				<-dead
			}()

			select {
			case d := <-done:
				if !d {
					// Runnable's success metric failed, break out of the chain
					break RunLoop
				}
			case <-kill:
				break RunLoop
			}
		}

		// ensure an event has occured, we may be here because all runnables completed
		<-kill

		// ensure all runnables (procs) are dead before restarting the chain
		wg.Wait()

		// check if we should quit, currently only used by test suites for teardown
		select {
		case <-quit:
			log.Warn("Quitting run chain")
			return
		default:
		}
	}
}

func newWatcher() *Watcher {
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		panic(err)
	}

	ignore, err := regexp.Compile(*ignoreRegex)
	if err != nil {
		log.Fatal("Ignore regex compile error:", err)(1)
	}

	watchDir := func(path string, info os.FileInfo, err error) error {
		if !info.IsDir() {
			return nil
		}

		if err != nil {
			log.Err("Error reading dir, skipping:", path, err)
			return filepath.SkipDir
		}

		if ignore.MatchString(path) {
			log.Debug("Ignoring dir:", path)
			return filepath.SkipDir
		}

		log.Debug("Watching dir:", path)
		err = watcher.Watch(path)
		if err != nil {
			log.Err("Error watching dir (path, error):", path, err)
		}

		return nil
	}

	if *recursive == true {
		// errors are handled in watchDir
		filepath.Walk(*targetDir, watchDir)
	} else {
		err = watcher.Watch(*targetDir)
		if err != nil {
			log.Fatal("Error watching dir (path, error):", *targetDir, err)(1)
		}
	}

	// To facilitate testing (which sends artifical events from a timer),
	// we have an abstracted struct Watcher that holds the applicable channels.
	// fsnotify.FileEvent is a fmt.Stringer, but channels cannot be converted.
	// Unfortunately, an extra channel is necessary to perform the conversion.
	event := make(chan fmt.Stringer)
	go func() {
		for {
			event <- <-watcher.Event
		}
	}()

	return &Watcher{event, watcher.Error}
}

func startWebServer() {
	if *webServer != "" {
		go func() {
			log.Info("Starting web server on port", *webServer)
			err := http.ListenAndServe(*webServer, http.FileServer(http.Dir(*targetDir)))
			if err != nil {
				log.Fatal("Error starting web server:", err)(2)
			}
		}()
	}
}

func configSetup() {
	runtime.GOMAXPROCS(runtime.NumCPU())

	flag.Usage = func() {
		fmt.Println("WaGo (Watch, Go) build tool. Usage:")
		flag.PrintDefaults()
	}

	// TODO: this should check for actions
	if len(os.Args) < 2 {
		flag.Usage()
		log.Fatal("You must specify an action")(1)
	}

	flag.Parse()

	if *verbose {
		log = dog.NewDog(dog.DEBUG)
	} else if *quiet {
		log = dog.NewDog(dog.WARN)
	} else {
		log = dog.NewDog(dog.INFO)
	}

	if len(*shell) == 0 {
		*shell = os.Getenv("SHELL")
		if len(*shell) == 0 {
			*shell = "/bin/sh"
		}
	}
	log.Debug("Using shell", *shell)

	if (len(*daemonTrigger) > 0) && (*daemonTimer > 0) {
		log.Fatal("Both daemon trigger and timer specified, use only one")(1)
	}

	if (len(*daemonTrigger) > 0 || *daemonTimer > 0) && len(*daemonCmd) == 0 {
		log.Fatal("Specify a daemon command to use the trigger or timer")(1)
	}

	if len(*buildCmd) == 0 && len(*daemonCmd) == 0 && !*fiddle && len(*postCmd) == 0 && len(*url) == 0 && len(*webServer) == 0 {
		flag.Usage()
		log.Fatal("You must specify an action")(1)
	}

	if *fiddle {
		if *webServer == "" {
			*webServer = ":9933"
		}
		if *url == "" {
			*url = "http://localhost" + *webServer + "/"
		}
	}

	if *targetDir == "" {
		cwd, err := os.Getwd()
		if err != nil {
			panic(err)
		}
		targetDir = &cwd
	}
	log.Debug("Target dir:", *targetDir)
}
<commit_msg>fix transposition<commit_after>// Wago (Watch, Go)
// A general purpose watch / build development tool.

// TODO: catch SIGINT and reset term
// see https://askubuntu.com/questions/171449/shell-does-not-show-typed-in-commands-reset-works-but-what-happened

package main

import (
	"flag"
	"fmt"
	"github.com/JonahBraun/dog"
	"github.com/howeyc/fsnotify"
	"net/http"
	"os"
	"path/filepath"
	"regexp"
	"runtime"
	"sync"
)

var (
	log     = dog.NewDog(dog.DEBUG)
	verbose = flag.Bool("v", false, "Verbose")
	quiet   = flag.Bool("q", false, "Quiet, only warnings and errors")

	buildCmd      = flag.String("cmd", "", "Run command, wait for it to complete.")
	daemonCmd     = flag.String("daemon", "", "Run command and leave running in the background.")
	daemonTimer   = flag.Int("timer", 0, "Wait miliseconds after starting daemon, then continue.")
	daemonTrigger = flag.String("trigger", "", "Wait for daemon to output this string, then continue.")
	exitWait      = flag.Int("exitwait", 50, "Max miliseconds a process has after a SIGTERM to exit before a SIGKILL.")
	fiddle        = flag.Bool("fiddle", false, "CLI fiddle mode! Start a web server, open browser to URL of targetDir/index.html")
	postCmd       = flag.String("pcmd", "", "Run command after daemon starts. Use this to kick off your test suite.")
	recursive     = flag.Bool("recursive", true, "Watch directory tree recursively.")
	targetDir     = flag.String("dir", "", "Directory to watch, defaults to current.")
	url           = flag.String("url", "", "Open browser to this URL after all commands are successful.")
	watchRegex    = flag.String("watch", `/\w[\w\.]*": (CREATE|MODIFY)`, "React to FS events matching regex. Use -v to see all events.")
	ignoreRegex   = flag.String("ignore", `\.(git|hg|svn)`, "Ignore directories matching regex.")
	webServer     = flag.String("web", "", "Start a web server at this address, e.g. :8420")
	shell         = flag.String("shell", "", "Shell used to run commands, defaults to $SHELL, fallback to /bin/sh")
)

type Watcher struct {
	Event chan fmt.Stringer
	Error chan error
}

func main() {
	// the following function calls merely serve to logically organize what
	// is otherwise a VERY lengthy setup

	// TODO: have configSetup return a config object so that the reliance on
	// config globals is removed
	configSetup()

	startWebServer()

	runChain(newWatcher(), make(chan struct{}))
}

// event loop and action chain happen here
func runChain(watcher *Watcher, quit chan struct{}) {
	chain := make([]Runnable, 0, 5)

	// build chain of runnables
	if len(*buildCmd) > 0 {
		chain = append(chain, NewRunWait(*buildCmd))
	}
	if len(*daemonCmd) > 0 {
		if len(*daemonTrigger) > 0 {
			chain = append(chain, NewDaemonTrigger(*daemonCmd, *daemonTrigger))
		} else {
			chain = append(chain, NewDaemonTimer(*daemonCmd, *daemonTimer))
		}
	}
	if len(*postCmd) > 0 {
		chain = append(chain, NewRunWait(*postCmd))
	}
	if *url != "" {
		chain = append(chain, NewBrowser(*url))
	}

	eventRegex, err := regexp.Compile(*watchRegex)
	if err != nil {
		log.Fatal("Watch regex compile error:", err)(1)
	}

	var wg sync.WaitGroup

	// main loop
	for {
		// kill is passed to all Runnable so they know when they should exit
		kill := make(chan struct{})

		var drain func()
		drain = func() {
			select {
			case ev := <-watcher.Event:
				log.Debug("Extra event ignored:", ev.String())
				drain()
			default:
			}
		}
		drain()

		// event loop
		go func() {
			for {
				select {
				case ev := <-watcher.Event:
					if eventRegex.MatchString(ev.String()) {
						log.Info("Matched event:", ev.String())
						close(kill)
						return
					} else {
						log.Debug("Ignored event:", ev.String())
					}
				case err = <-watcher.Error:
					log.Fatal("Watcher error:", err)(5)
				case <-quit:
					// currently only used by test suite
					close(kill)
					return
				}
			}
		}()

	RunLoop:
		for _, runnable := range chain {
			done, dead := runnable(kill)
			wg.Add(1)

			go func() {
				<-dead
				wg.Done()
			}()

			select {
			case d := <-done:
				if !d {
					// Runnable's success metric failed, break out of the chain
					break RunLoop
				}
			case <-kill:
				break RunLoop
			}
		}

		// ensure an event has occured, we may be here because all runnables completed
		<-kill

		// ensure all runnables (procs) are dead before restarting the chain
		wg.Wait()

		// check if we should quit, currently only used by test suites for teardown
		select {
		case <-quit:
			log.Warn("Quitting run chain")
			return
		default:
		}
	}
}

func newWatcher() *Watcher {
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		panic(err)
	}

	ignore, err := regexp.Compile(*ignoreRegex)
	if err != nil {
		log.Fatal("Ignore regex compile error:", err)(1)
	}

	watchDir := func(path string, info os.FileInfo, err error) error {
		if !info.IsDir() {
			return nil
		}

		if err != nil {
			log.Err("Error reading dir, skipping:", path, err)
			return filepath.SkipDir
		}

		if ignore.MatchString(path) {
			log.Debug("Ignoring dir:", path)
			return filepath.SkipDir
		}

		log.Debug("Watching dir:", path)
		err = watcher.Watch(path)
		if err != nil {
			log.Err("Error watching dir (path, error):", path, err)
		}

		return nil
	}

	if *recursive == true {
		// errors are handled in watchDir
		filepath.Walk(*targetDir, watchDir)
	} else {
		err = watcher.Watch(*targetDir)
		if err != nil {
			log.Fatal("Error watching dir (path, error):", *targetDir, err)(1)
		}
	}

	// To facilitate testing (which sends artifical events from a timer),
	// we have an abstracted struct Watcher that holds the applicable channels.
	// fsnotify.FileEvent is a fmt.Stringer, but channels cannot be converted.
	// Unfortunately, an extra channel is necessary to perform the conversion.
	event := make(chan fmt.Stringer)
	go func() {
		for {
			event <- <-watcher.Event
		}
	}()

	return &Watcher{event, watcher.Error}
}

func startWebServer() {
	if *webServer != "" {
		go func() {
			log.Info("Starting web server on port", *webServer)
			err := http.ListenAndServe(*webServer, http.FileServer(http.Dir(*targetDir)))
			if err != nil {
				log.Fatal("Error starting web server:", err)(2)
			}
		}()
	}
}

func configSetup() {
	runtime.GOMAXPROCS(runtime.NumCPU())

	flag.Usage = func() {
		fmt.Println("WaGo (Watch, Go) build tool. Usage:")
		flag.PrintDefaults()
	}

	// TODO: this should check for actions
	if len(os.Args) < 2 {
		flag.Usage()
		log.Fatal("You must specify an action")(1)
	}

	flag.Parse()

	if *verbose {
		log = dog.NewDog(dog.DEBUG)
	} else if *quiet {
		log = dog.NewDog(dog.WARN)
	} else {
		log = dog.NewDog(dog.INFO)
	}

	if len(*shell) == 0 {
		*shell = os.Getenv("SHELL")
		if len(*shell) == 0 {
			*shell = "/bin/sh"
		}
	}
	log.Debug("Using shell", *shell)

	if (len(*daemonTrigger) > 0) && (*daemonTimer > 0) {
		log.Fatal("Both daemon trigger and timer specified, use only one")(1)
	}

	if (len(*daemonTrigger) > 0 || *daemonTimer > 0) && len(*daemonCmd) == 0 {
		log.Fatal("Specify a daemon command to use the trigger or timer")(1)
	}

	if len(*buildCmd) == 0 && len(*daemonCmd) == 0 && !*fiddle && len(*postCmd) == 0 && len(*url) == 0 && len(*webServer) == 0 {
		flag.Usage()
		log.Fatal("You must specify an action")(1)
	}

	if *fiddle {
		if *webServer == "" {
			*webServer = ":9933"
		}
		if *url == "" {
			*url = "http://localhost" + *webServer + "/"
		}
	}

	if *targetDir == "" {
		cwd, err := os.Getwd()
		if err != nil {
			panic(err)
		}
		targetDir = &cwd
	}
	log.Debug("Target dir:", *targetDir)
}
<|endoftext|>


<commit_before>package main

import (
    "log"
    "strings"
    "net/url"
    "github.com/PuerkitoBio/goquery"
)

const LSE_URL = "http://www.lse.ac.uk"
var PROGRAMMES_URL = map[string]string {
    "Undergraduate":    LSE_URL + "/resources/calendar/courseGuides/undergraduate.htm",
    "Graduate":         LSE_URL + "/resources/calendar/courseGuides/graduate.htm",
    "Research":         LSE_URL + "/resources/calendar/courseGuides/research.htm",
}

type Course struct {
    Code string
    Title string
    URL string
    Department string
    Students int
    Class int
    Value int
    Program int
}

func Parse(Type string) (program_courses []interface{}) {
    var program *goquery.Document
    var e error

    if program, e = goquery.NewDocument(PROGRAMMES_URL[Type]); e != nil {
        log.Fatal(e)
    }

    program.Find("table tr td p a").Each(func(i int, s *goquery.Selection) {
        course_item := strings.Split(s.Text(), " ")
        course_item_url, _ := s.Attr("href")
        parsed_url, _ := url.Parse(PROGRAMMES_URL[Type])
        parsed_relative, _ := url.Parse(course_item_url)

        course_object := new(Course)
        course_object.Code = course_item[0]
        course_object.Title = course_item[1]
        course_object.URL = parsed_url.ResolveReference(parsed_relative).String()
        course_object.Program = 0 // TODO
        program_courses = append(program_courses, course_object)
    })
    return
}

func main() {
    var courses []interface{}
    for program, _ := range PROGRAMMES_URL {
        courses = append(courses, Parse(program)...)
        log.Printf("%s courses: %d", program, len(courses))
    }
}<commit_msg>More compartmentalizing and fixed type<commit_after>package main

import (
    "log"
    "strings"
    "net/url"
    "github.com/PuerkitoBio/goquery"
)

const LSE_URL = "http://www.lse.ac.uk"
var PROGRAMMES_URL = map[string]string {
    "Undergraduate":    LSE_URL + "/resources/calendar/courseGuides/undergraduate.htm",
    //"Graduate":         LSE_URL + "/resources/calendar/courseGuides/graduate.htm",
    //"Research":         LSE_URL + "/resources/calendar/courseGuides/research.htm",
}

type Course struct {
    code string
    title string
    url string
    department string
    students int
    class int
    value int
    program int
}

func getDocument(url string) (program *goquery.Document) {
    var e error
    if program, e = goquery.NewDocument(url); e != nil {
        log.Fatal(e)
    }
    return
}

func getCourses(Type string) (program_courses []Course) {
    program := getDocument(PROGRAMMES_URL[Type])

    program.Find("table tr td p a").Each(func(i int, s *goquery.Selection) {
        course_item := strings.Split(s.Text(), " ")
        course_item_url, _ := s.Attr("href")
        parsed_url, _ := url.Parse(PROGRAMMES_URL[Type])
        parsed_relative, _ := url.Parse(course_item_url)

        course_object := new(Course)
        course_object.code = course_item[0]
        course_object.title = course_item[1]
        course_object.url = parsed_url.ResolveReference(parsed_relative).String()
        course_object.program = 0 // TODO
        program_courses = append(program_courses, *course_object)
    })
    return
}

func main() {
    var courses []Course
    for program, _ := range PROGRAMMES_URL {
        courses = append(courses, getCourses(program)...)
    }
    log.Printf("Total courses: %d", len(courses))

    // Test
    for _, test_c := range courses {
        log.Print(test_c.Code())
    }
}<|endoftext|>


<commit_before>/*
 * Copyright 2014 Brian McCallister
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *

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

import (
	"github.com/codegangsta/martini"
	"github.com/martini-contrib/render"

	"github.com/brianm/dx/rep"
	"github.com/brianm/henry"
	"github.com/martini-contrib/binding"
)

func main() {
	m := martini.Classic()
	m.Use(render.Renderer())
	m.Map(henry.ReturnHandler())

	m.Post("/srv",
		binding.Bind(rep.Service{}),
		func(s rep.Service) (int, rep.Service) {
			return 201, s
		})

	m.Run()
}

type person struct {
	Name string `json:"name"`
	Age  int    `json:"age"`
}
<commit_msg>new henry api<commit_after>/*
 * Copyright 2014 Brian McCallister
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *

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

import (
	"github.com/codegangsta/martini"
	"github.com/martini-contrib/render"

	"github.com/brianm/dx/rep"
	"github.com/brianm/henry"
	"github.com/martini-contrib/binding"
)

func main() {
	m := martini.Classic()
	m.Use(render.Renderer())
	m.Map(henry.Negotiator())

	m.Post("/srv",
		binding.Bind(rep.Service{}),
		func(s rep.Service) (int, rep.Service) {
			return 201, s
		})

	m.Run()
}

type person struct {
	Name string `json:"name"`
	Age  int    `json:"age"`
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"time"
)

const (
	API_URL        string = "http://pubapi.cryptsy.com/api.php?method=singlemarketdata&marketid="
	DOGE_ID        int    = 132
	RETRY_ATTEMPTS int    = 5
	RETRY_DELAY           = 5
	LOG_FILE_NAME  string = ".wst_log"
)

var (
	currentRetryCount int = 0
	logFile           *os.File
)

// Represents the overall ApiResponse (made up of a market which has many trades)
type ApiResponse struct {
	Success int              `json:"success"`
	Return  MarketCollection `json:"return"`
}

// A wrapper around a map so that I don't need to convert from []interface{} or
// map[string]interface{}
type MarketCollection struct {
	Markets map[string]Market `json:"markets"`
}

// Represents a market on the cryptsy exchange
//
// A lot of these values should be float64s but annoyingly everything comes back as a string
// so conversions are gonna be needed to do anything worthwhile later.
type Market struct {
	MarketId       string  `json:"marketid"`
	Label          string  `json:"label"`
	LastTradePrice string  `json:"lasttradeprice"`
	Volume         string  `json:"volume"`
	LastTradeTime  string  `json:"lasttradetime"`
	PrimaryName    string  `json:"primaryname"`
	PrimaryCode    string  `json:"primarycode"`
	SecondaryName  string  `json:"secondaryname"`
	SecondaryCode  string  `json:"secondarycode"`
	RecentTrades   []Trade `json:"recenttrades"`
	SellOrders     []Order `json:"sellorders"`
	BuyOrders      []Order `json:"buyorders"`
}

// Represents a single trade of DOGE
type Trade struct {
	Id       string
	Time     string
	Price    string
	Quantity string
	Total    string
}

// Represents a single buy or sell order
type Order struct {
	Price    string
	Quantity string
	Total    string
}

func main() {
	openLogFile()
	defer closeLogFile()

	data := makeApiRequest()

	var apiresp ApiResponse

	err := json.Unmarshal(data, &apiresp)
	if err != nil {
		writeLogLine(fmt.Sprintf("Error occurred while parsing api response data: %v\n", err.Error()))
	}

	label := apiresp.Return.Markets["DOGE"].Label
	val := apiresp.Return.Markets["DOGE"].LastTradePrice

	writeLogLine(fmt.Sprintf("SUCCESS: %v : %v\n", label, val))
	fmt.Printf("%v : %v\n", label, val)
}

// Perform the actual HTTP request to the API and return the response
// as a string.  This will do some retrying if it gets a bad gateway error.
func makeApiRequest() []byte {
	reqUrl := fmt.Sprintf("%v%v", API_URL, DOGE_ID)

	resp, err := http.Get(reqUrl)
	if err != nil {
		writeLogLine(fmt.Sprintf("ERROR OCCURRED DURING HTTP REQUEST: %v\n", err.Error()))
	}

	status := resp.StatusCode
	if status != 200 {
		if status == 502 {
			currentRetryCount++
			if currentRetryCount > RETRY_ATTEMPTS {
				return nil
			} else {
				time.Sleep(time.Second * RETRY_DELAY)
				return makeApiRequest()
			}
		} else {
			writeLogLine(fmt.Sprintf("ERROR STATUS RETURNED: %v\n", status))
			return nil
		}
	}

	body := resp.Body
	bodyText, err := ioutil.ReadAll(body)
	body.Close()

	return bodyText
}

func getLogFilePath() string {
	path := os.Getenv("HOME")
	return path + "/" + LOG_FILE_NAME
}

// Sets the logFile file pointer to the log file
func openLogFile() {

	filePtr, err := os.OpenFile(getLogFilePath(), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
	if err != nil {
		fmt.Println("Couldn't open logfile for writing:", err.Error())
		return
	} else {
		logFile = filePtr
	}
}

// Closes the logFile if it is open
func closeLogFile() {
	if logFile != nil {
		logFile.Close()
	}
}

func writeLogLine(text string) {
	if logFile != nil {
		// Add the timestamp to the string
		currentTime := time.Now()
		writeOut := fmt.Sprintf("%v : %v", currentTime, text)
		bytes := []byte(writeOut)
		_, err := logFile.Write(bytes)
		if err != nil {
			fmt.Println("ERROR WRITING TO LOGFILE:", err.Error())
		}
	}
}
<commit_msg>Fix a potential screwup with writing the file to /<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"time"
)

const (
	API_URL        string = "http://pubapi.cryptsy.com/api.php?method=singlemarketdata&marketid="
	DOGE_ID        int    = 132
	RETRY_ATTEMPTS int    = 5
	RETRY_DELAY           = 5
	LOG_FILE_NAME  string = ".wst_log"
)

var (
	currentRetryCount int = 0
	logFile           *os.File
)

// Represents the overall ApiResponse (made up of a market which has many trades)
type ApiResponse struct {
	Success int              `json:"success"`
	Return  MarketCollection `json:"return"`
}

// A wrapper around a map so that I don't need to convert from []interface{} or
// map[string]interface{}
type MarketCollection struct {
	Markets map[string]Market `json:"markets"`
}

// Represents a market on the cryptsy exchange
//
// A lot of these values should be float64s but annoyingly everything comes back as a string
// so conversions are gonna be needed to do anything worthwhile later.
type Market struct {
	MarketId       string  `json:"marketid"`
	Label          string  `json:"label"`
	LastTradePrice string  `json:"lasttradeprice"`
	Volume         string  `json:"volume"`
	LastTradeTime  string  `json:"lasttradetime"`
	PrimaryName    string  `json:"primaryname"`
	PrimaryCode    string  `json:"primarycode"`
	SecondaryName  string  `json:"secondaryname"`
	SecondaryCode  string  `json:"secondarycode"`
	RecentTrades   []Trade `json:"recenttrades"`
	SellOrders     []Order `json:"sellorders"`
	BuyOrders      []Order `json:"buyorders"`
}

// Represents a single trade of DOGE
type Trade struct {
	Id       string
	Time     string
	Price    string
	Quantity string
	Total    string
}

// Represents a single buy or sell order
type Order struct {
	Price    string
	Quantity string
	Total    string
}

func main() {
	openLogFile()
	defer closeLogFile()

	data := makeApiRequest()

	var apiresp ApiResponse

	err := json.Unmarshal(data, &apiresp)
	if err != nil {
		writeLogLine(fmt.Sprintf("Error occurred while parsing api response data: %v\n", err.Error()))
	}

	label := apiresp.Return.Markets["DOGE"].Label
	val := apiresp.Return.Markets["DOGE"].LastTradePrice

	writeLogLine(fmt.Sprintf("SUCCESS: %v : %v\n", label, val))
	fmt.Printf("%v : %v\n", label, val)
}

// Perform the actual HTTP request to the API and return the response
// as a string.  This will do some retrying if it gets a bad gateway error.
func makeApiRequest() []byte {
	reqUrl := fmt.Sprintf("%v%v", API_URL, DOGE_ID)

	resp, err := http.Get(reqUrl)
	if err != nil {
		writeLogLine(fmt.Sprintf("ERROR OCCURRED DURING HTTP REQUEST: %v\n", err.Error()))
	}

	status := resp.StatusCode
	if status != 200 {
		if status == 502 {
			currentRetryCount++
			if currentRetryCount > RETRY_ATTEMPTS {
				return nil
			} else {
				time.Sleep(time.Second * RETRY_DELAY)
				return makeApiRequest()
			}
		} else {
			writeLogLine(fmt.Sprintf("ERROR STATUS RETURNED: %v\n", status))
			return nil
		}
	}

	body := resp.Body
	bodyText, err := ioutil.ReadAll(body)
	body.Close()

	return bodyText
}

func getLogFilePath() string {
	path := os.Getenv("HOME")
	pathStr := ""
	if path != "" {
		pathStr = path + "/" + LOG_FILE_NAME
	} else {
		pathStr = LOG_FILE_NAME
	}

	return pathStr

}

// Sets the logFile file pointer to the log file
func openLogFile() {

	filePtr, err := os.OpenFile(getLogFilePath(), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
	if err != nil {
		fmt.Println("Couldn't open logfile for writing:", err.Error())
		return
	} else {
		logFile = filePtr
	}
}

// Closes the logFile if it is open
func closeLogFile() {
	if logFile != nil {
		logFile.Close()
	}
}

func writeLogLine(text string) {
	if logFile != nil {
		// Add the timestamp to the string
		currentTime := time.Now()
		writeOut := fmt.Sprintf("%v : %v", currentTime, text)
		bytes := []byte(writeOut)
		_, err := logFile.Write(bytes)
		if err != nil {
			fmt.Println("ERROR WRITING TO LOGFILE:", err.Error())
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"time"
)

// WRONG!!! For val variable in the above loops is actually a single variable.
// func wrong1(values []int) {
// 	for val := range values {
// 		// go val.my_method()
// 		go func() {
// 			fmt.Print(val)
// 		}()
// 	}
// }

// RIGHT!!! The val is evaluated at each iteration and placed on the stack for the goroutine.
func right1(values []int) {
	for val := range values {
		go func(val interface{}) {
			fmt.Print(val)
		}(val)
	}
}

// RIGHT!!! The variables declared within the body of a loop are not shared between iterations, and thus can be used separately in a closure.
func right2(values []int) {
	for i := range values {
		val := values[i]
		go func() {
			fmt.Print(val)
		}()
	}
}

// RIGHT!!! Without executing this closure as a goroutine, the code runs as expected
func right3(values []int) {
	for i := 0; i < 10; i++ {
		func() {
			fmt.Print(i)
		}()
	}
}
func main() {
	values := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

	// wrong1(values)
	// right1(values)
	// right2(values)
	right3(values)
	time.Sleep(time.Second)
}
<commit_msg>little<commit_after>package main

import (
	"bytes"
	"encoding/base64"
	"fmt"
	"io"
	"os"
)

func main() {
	var b bytes.Buffer // A Buffer needs no initialization.
	b.Write([]byte("Hello "))
	fmt.Fprintf(&b, "world!\n")
	b.WriteTo(os.Stdout)

	// A Buffer can turn a string or a []byte into an io.Reader.
	buf := bytes.NewBufferString("R29waGVycyBydWxlIQ==")
	dec := base64.NewDecoder(base64.StdEncoding, buf)
	io.Copy(os.Stdout, dec)
}
<|endoftext|>


<commit_before>// dither project main.go
package main

import (
	"flag"
	"fmt"
	"image"
	"image/color"
	"log"
	"math"
	"os"
	"sync"

	"math/rand"

	_ "image/gif"
	_ "image/jpeg"
	_ "image/png"

	"github.com/nfnt/resize"
	"golang.org/x/image/tiff"
)

var (
	horzontalBlocks uint
	seed            int64
	smooth          bool
	gamma           float64
)

const A = 0.985

func main() {
	flag.UintVar(&horzontalBlocks, "b", 0, "Block pixels on horizontal side")
	flag.Int64Var(&seed, "r", 0, "Random number seed for dithering")
	flag.BoolVar(&smooth, "s", false, "Produce smoother look")
	flag.Float64Var(&gamma, "g", 2.2, "Gamma of input image")
	flag.Parse()
	gammaInit()

	await := &sync.WaitGroup{}
	for _, fname := range flag.Args() {
		await.Add(1)
		go func(filename string) {
			defer await.Done()
			dithered := ditherImage(imgFromFName(filename))
			save(dithered, filename)
		}(fname)
	}
	await.Wait()
}

func imgFromFName(fname string) image.Image {
	f, err := os.Open(fname)
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()

	img, _, err := image.Decode(f)
	if err != nil {
		log.Fatal(err)
	}

	return img
}

func save(i image.Image, name string) {
	var typeName string
	if smooth {
		typeName = "s"
	} else {
		typeName = "d"
	}
	w, err := os.Create(fmt.Sprintf("%s.%s%04d.tiff", name, typeName, horzontalBlocks))
	if err != nil {
		log.Fatal(err)
	}
	defer w.Close()

	err = tiff.Encode(w, i, &tiff.Options{Compression: tiff.Deflate, Predictor: true})
	if err != nil {
		log.Fatal(err)
	}
}

var white = color.Gray16{Y: 65535}
var black = color.Gray16{Y: 0}

func ditherImage(i image.Image) image.Image {
	if horzontalBlocks == 0 {
		return ditherImage1to1(i)
	}

	smaller := resize.Resize(horzontalBlocks, 0, i, resize.Lanczos3)
	dith := ditherImage1to1(smaller)
	finalWidth := uint(i.Bounds().Size().X)
	finalHeight := uint(i.Bounds().Size().Y)
	if smooth {
		return resize.Resize(finalWidth, finalHeight, dith, resize.Lanczos3)
	} else {
		return resize.Resize(finalWidth, finalHeight, dith, resize.NearestNeighbor)
	}
}

func ditherImage1to1(i image.Image) image.Image {
	b := i.Bounds()
	d := image.NewGray16(b)
	r := rand.New(rand.NewSource(seed))

	for y := b.Min.Y; y < b.Max.Y; y += 1 {
		for x := b.Min.X; x < b.Max.X; x += 1 {
			value := color.Gray16Model.Convert(i.At(x, y)).(color.Gray16)
			rand := uint16(r.Uint32())
			if rand < lut[value.Y] {
				d.Set(x, y, white)
			} else {
				d.Set(x, y, black)
			}
		}
	}

	return d
}

func gammaDecode(in float64) float64 {
	return A * math.Pow(in, gamma)
}

var lut []uint16

func gammaInit() {
	lut = make([]uint16, 65536)
	for i := 0; i < 65536; i++ {
		lut[i] = uint16(gammaDecode(float64(i)/65536.0) * 65536.0)
	}
}
<commit_msg>Specify horintal edge, vertical edge, or both.<commit_after>// dither project main.go
package main

import (
	"flag"
	"fmt"
	"image"
	"image/color"
	"log"
	"math"
	"os"
	"sync"

	"math/rand"

	_ "image/gif"
	_ "image/jpeg"
	_ "image/png"

	"github.com/nfnt/resize"
	"golang.org/x/image/tiff"
)

var (
	xBlocks int
	yBlocks int
	seed    int64
	smooth  bool
	gamma   float64
)

const A = 0.985

func main() {
	flag.IntVar(&xBlocks, "x", 0, "Block pixels on horizontal side")
	flag.IntVar(&yBlocks, "y", 0, "Block pixels on vertical side")
	flag.Int64Var(&seed, "r", 0, "Random number seed for dithering")
	flag.BoolVar(&smooth, "s", false, "Produce smoother look")
	flag.Float64Var(&gamma, "g", 2.2, "Gamma of input image")
	flag.Parse()
	gammaInit()

	await := &sync.WaitGroup{}
	for _, fname := range flag.Args() {
		await.Add(1)
		go func(filename string) {
			defer await.Done()
			dithered := ditherImage(imgFromFName(filename))
			save(dithered, filename)
		}(fname)
	}
	await.Wait()
}

func imgFromFName(fname string) image.Image {
	f, err := os.Open(fname)
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()

	img, _, err := image.Decode(f)
	if err != nil {
		log.Fatal(err)
	}

	return img
}

func save(i image.Image, name string) {
	var typeName string
	if smooth {
		typeName = "s"
	} else {
		typeName = "d"
	}

	var (
		sizeX int
		sizeY int
	)

	switch {
	case xBlocks != 0 && yBlocks != 0:
		sizeX = xBlocks
		sizeY = yBlocks

	case xBlocks == 0 && yBlocks != 0:
		sizeX = yBlocks * i.Bounds().Size().X / i.Bounds().Size().Y
		sizeY = yBlocks

	case xBlocks != 0 && yBlocks == 0:
		sizeX = xBlocks
		sizeY = xBlocks * i.Bounds().Size().Y / i.Bounds().Size().X

	default:
		sizeX = i.Bounds().Size().X
		sizeY = i.Bounds().Size().Y
	}

	w, err := os.Create(fmt.Sprintf("%s.%s%04dx%04d.tiff", name, typeName, sizeX, sizeY))
	if err != nil {
		log.Fatal(err)
	}
	defer w.Close()

	err = tiff.Encode(w, i, &tiff.Options{Compression: tiff.Deflate, Predictor: true})
	if err != nil {
		log.Fatal(err)
	}
}

var white = color.Gray16{Y: 65535}
var black = color.Gray16{Y: 0}

func ditherImage(i image.Image) image.Image {
	if xBlocks == 0 && yBlocks == 0 {
		return ditherImage1to1(i)
	}

	smaller := resize.Resize(uint(xBlocks), uint(yBlocks), i, resize.Lanczos3)
	dith := ditherImage1to1(smaller)
	finalWidth := uint(i.Bounds().Size().X)
	finalHeight := uint(i.Bounds().Size().Y)
	if smooth {
		return resize.Resize(finalWidth, finalHeight, dith, resize.Lanczos3)
	} else {
		return resize.Resize(finalWidth, finalHeight, dith, resize.NearestNeighbor)
	}
}

func ditherImage1to1(i image.Image) image.Image {
	b := i.Bounds()
	d := image.NewGray16(b)
	r := rand.New(rand.NewSource(seed))

	for y := b.Min.Y; y < b.Max.Y; y += 1 {
		for x := b.Min.X; x < b.Max.X; x += 1 {
			value := color.Gray16Model.Convert(i.At(x, y)).(color.Gray16)
			rand := uint16(r.Uint32())
			if rand < lut[value.Y] {
				d.Set(x, y, white)
			} else {
				d.Set(x, y, black)
			}
		}
	}

	return d
}

func gammaDecode(in float64) float64 {
	return A * math.Pow(in, gamma)
}

var lut []uint16

func gammaInit() {
	lut = make([]uint16, 65536)
	for i := 0; i < 65536; i++ {
		lut[i] = uint16(gammaDecode(float64(i)/65536.0) * 65536.0)
	}
}
<|endoftext|>


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

package main

import "agenda/agenda/cmd"

func main() {
	cmd.Execute()
}
<commit_msg>modified import path from agenda/agenda/cmd to agenda/cmd<commit_after>// Copyright © 2017 NAME HERE <EMAIL ADDRESS>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import "agenda/cmd"

func main() {
	cmd.Execute()
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"log/syslog"
	"net"
	"os"
	"os/signal"
	"path/filepath"
	"strings"
	"syscall"
	"time"

	"golang.org/x/sys/unix"

	"github.com/vishvananda/netlink"

	"gopkg.in/yaml.v2"
)

func init() {
	flag.Parse()
}

var (
	l             *syslog.Writer
	master_iface  string   = "vlan1001"
	ipset_support          = true
	xmlnsuri               = "http://simplecloud.ru/"
	servers       *Servers = NewServers()
	flagVersion            = flag.Bool("v", false, "display version string")
	flagInstall            = flag.Bool("i", false, "install service")
	Version                = ""
	BuildTime              = ""
)

func main() {
	var err error
	var buf []byte
	var data map[string]string

	if *flagVersion {
		fmt.Printf("%s build %s\n", Version, BuildTime)
		os.Exit(0)
	}

	if *flagInstall {
		if err = installService(); err != nil {
			log.Fatalf("failed to install service: %s\n", err.Error())
			os.Exit(1)
		}
		os.Exit(0)
	}

	l, err = syslog.Dial("", "", syslog.LOG_DAEMON|syslog.LOG_INFO, filepath.Base(os.Args[0]))
	if err != nil {
		log.Fatalf("Failed to connect to syslog: %s\n", err.Error())
		os.Exit(1)
	}
	defer l.Close()

	if buf, err = ioutil.ReadFile("/etc/svirtnet.yml"); err == nil {
		if err = yaml.Unmarshal(buf, &data); err == nil {
			if val, ok := data["interface"]; ok && val != "" {
				master_iface = val
			}
			if val, ok := data["ipset_support"]; ok {
				if val == "false" {
					ipset_support = false
				}
			}
			if val, ok := data["xmlnsuri"]; ok && val != "" {
				xmlnsuri = val
			}
		}
	}

	l.Info("ListenAndServeTCPv4")
	go ListenAndServeTCPv4()

	lnkupdate := make(chan netlink.LinkUpdate)
	lnkdone := make(chan struct{})
	err = netlink.LinkSubscribe(lnkupdate, lnkdone)
	if err != nil {
		l.Err(err.Error())
		os.Exit(1)
	}
	defer close(lnkdone)

	ifaces, err := net.Interfaces()
	if err != nil {
		l.Err(err.Error())
		os.Exit(1)
	}

	for _, iface := range ifaces {
		name := iface.Name
		l.Info("Check iface " + name)
		if !strings.HasPrefix(name, "tap") {
			continue
		}
		servers.Lock()
		if _, ok := servers.Get(name[3:]); !ok {
			s := &Server{name: name[3:]}
			servers.Add(name[3:], s)
			l.Info(name[3:] + " start serving")
			wait := make(chan struct{})
			go func() {
				defer func() {
					if r := recover(); r != nil {
						err, ok := r.(error)
						if !ok {
							err = fmt.Errorf("pkg: %v", r)
						}
						fmt.Printf(err.Error())
					}
				}()
				if err := s.Start(); err != nil {
					panic(err)
				}
				close(wait)
			}()
			<-wait
		}
		servers.Unlock()
	}

	sg := make(chan os.Signal, 1)
	signal.Notify(sg, unix.SIGINT, unix.SIGQUIT, unix.SIGTERM, unix.SIGHUP)

	tk := time.NewTicker(2 * time.Minute)
	defer tk.Stop()
	for {
		select {
		case <-tk.C:
			servers.Lock()
			for _, s := range servers.List() {
				t := time.Now().Add(5 * time.Minute)
				if !s.downtime.IsZero() && t.After(s.downtime) {
					servers.Del(s.name)
				}
			}
			servers.Unlock()
		case signame := <-sg:
			fmt.Println("Got signal:", signame)
			switch signame {
			case syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM:
				servers.Lock()
				for _, s := range servers.List() {
					wait := make(chan struct{})
					go func() {
						defer func() {
							if r := recover(); r != nil {
								err, ok := r.(error)
								if !ok {
									err = fmt.Errorf("pkg: %v", r)
								}
								fmt.Printf(err.Error())
							}
						}()
						if err := s.Stop(false); err != nil {
							panic(err)
						}
						close(wait)
					}()
					<-wait
					servers.Del(s.name)
				}
				servers.Unlock()
				os.Exit(0)
			}
		case msg := <-lnkupdate:
			if !strings.HasPrefix(msg.Attrs().Name, "tap") {
				continue
			}
			switch msg.Header.Type {
			case unix.RTM_NEWLINK:
				if msg.Change == unix.IFF_UP {
					//					fmt.Printf("newlink %#+v\n", msg)
					name := msg.Attrs().Name[3:]
					servers.Lock()
					if s, ok := servers.Get(name); !ok {
						s = &Server{name: name}
						servers.Add(name, s)
						go func() {
							defer func() {
								if r := recover(); r != nil {
									err, ok := r.(error)
									if !ok {
										err = fmt.Errorf("pkg: %v", r)
									}
									fmt.Printf(err.Error())
								}
							}()
							if err := s.Start(); err != nil {
								panic(err)
							}
						}()
					} else {
						go func() {
							defer func() {
								if r := recover(); r != nil {
									err, ok := r.(error)
									if !ok {
										err = fmt.Errorf("pkg: %v", r)
									}
									fmt.Printf(err.Error())
								}
							}()
							if err := s.Start(); err != nil {
								panic(err)
							}
						}()
					}
					servers.Unlock()
				}
			case unix.RTM_DELLINK:
				//				if msg.Change == unix.IFF_UP {
				//				fmt.Printf("dellink %#+v\n", msg)
				servers.Lock()
				name := msg.Attrs().Name[3:]
				if s, ok := servers.Get(name); ok {
					go func() {
						defer func() {
							if r := recover(); r != nil {
								err, ok := r.(error)
								if !ok {
									err = fmt.Errorf("pkg: %v", r)
								}
								fmt.Printf(err.Error())
							}
						}()
						if err := s.Stop(true); err != nil {
							panic(err)
						}
					}()
				}
				servers.Unlock()
				//				}
			}
		}
	}

}
<commit_msg>try some configs<commit_after>package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"log/syslog"
	"net"
	"os"
	"os/signal"
	"path/filepath"
	"strings"
	"syscall"
	"time"

	"golang.org/x/sys/unix"

	"github.com/vishvananda/netlink"

	"gopkg.in/yaml.v2"
)

func init() {
	flag.Parse()
}

var (
	l             *syslog.Writer
	master_iface  string   = "vlan1001"
	ipset_support          = true
	xmlnsuri               = "http://simplecloud.ru/"
	servers       *Servers = NewServers()
	flagVersion            = flag.Bool("v", false, "display version string")
	flagInstall            = flag.Bool("i", false, "install service")
	Version                = ""
	BuildTime              = ""
)

func main() {
	var err error
	var buf []byte
	var data map[string]string

	if *flagVersion {
		fmt.Printf("%s build %s\n", Version, BuildTime)
		os.Exit(0)
	}

	if *flagInstall {
		if err = installService(); err != nil {
			log.Fatalf("failed to install service: %s\n", err.Error())
			os.Exit(1)
		}
		os.Exit(0)
	}

	l, err = syslog.Dial("", "", syslog.LOG_DAEMON|syslog.LOG_INFO, filepath.Base(os.Args[0]))
	if err != nil {
		log.Fatalf("Failed to connect to syslog: %s\n", err.Error())
		os.Exit(1)
	}
	defer l.Close()

	for _, fname := range []string{"svirtnet.yml", "cloudmeta.yaml"} {
		if buf, err = ioutil.ReadFile(filepath.Join("/etc", fname)); err == nil {
			if err = yaml.Unmarshal(buf, &data); err == nil {
				if val, ok := data["interface"]; ok && val != "" {
					master_iface = val
				}
				if val, ok := data["ipset_support"]; ok {
					if val == "false" {
						ipset_support = false
					}
				}
				if val, ok := data["xmlnsuri"]; ok && val != "" {
					xmlnsuri = val
				}
			}
		}
	}

	l.Info("ListenAndServeTCPv4")
	go ListenAndServeTCPv4()

	lnkupdate := make(chan netlink.LinkUpdate)
	lnkdone := make(chan struct{})
	err = netlink.LinkSubscribe(lnkupdate, lnkdone)
	if err != nil {
		l.Err(err.Error())
		os.Exit(1)
	}
	defer close(lnkdone)

	ifaces, err := net.Interfaces()
	if err != nil {
		l.Err(err.Error())
		os.Exit(1)
	}

	for _, iface := range ifaces {
		name := iface.Name
		l.Info("Check iface " + name)
		if !strings.HasPrefix(name, "tap") {
			continue
		}
		servers.Lock()
		if _, ok := servers.Get(name[3:]); !ok {
			s := &Server{name: name[3:]}
			servers.Add(name[3:], s)
			l.Info(name[3:] + " start serving")
			wait := make(chan struct{})
			go func() {
				defer func() {
					if r := recover(); r != nil {
						err, ok := r.(error)
						if !ok {
							err = fmt.Errorf("pkg: %v", r)
						}
						fmt.Printf(err.Error())
					}
				}()
				if err := s.Start(); err != nil {
					panic(err)
				}
				close(wait)
			}()
			<-wait
		}
		servers.Unlock()
	}

	sg := make(chan os.Signal, 1)
	signal.Notify(sg, unix.SIGINT, unix.SIGQUIT, unix.SIGTERM, unix.SIGHUP)

	tk := time.NewTicker(2 * time.Minute)
	defer tk.Stop()
	for {
		select {
		case <-tk.C:
			servers.Lock()
			for _, s := range servers.List() {
				t := time.Now().Add(5 * time.Minute)
				if !s.downtime.IsZero() && t.After(s.downtime) {
					servers.Del(s.name)
				}
			}
			servers.Unlock()
		case signame := <-sg:
			fmt.Println("Got signal:", signame)
			switch signame {
			case syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM:
				servers.Lock()
				for _, s := range servers.List() {
					wait := make(chan struct{})
					go func() {
						defer func() {
							if r := recover(); r != nil {
								err, ok := r.(error)
								if !ok {
									err = fmt.Errorf("pkg: %v", r)
								}
								fmt.Printf(err.Error())
							}
						}()
						if err := s.Stop(false); err != nil {
							panic(err)
						}
						close(wait)
					}()
					<-wait
					servers.Del(s.name)
				}
				servers.Unlock()
				os.Exit(0)
			}
		case msg := <-lnkupdate:
			if !strings.HasPrefix(msg.Attrs().Name, "tap") {
				continue
			}
			switch msg.Header.Type {
			case unix.RTM_NEWLINK:
				if msg.Change == unix.IFF_UP {
					//					fmt.Printf("newlink %#+v\n", msg)
					name := msg.Attrs().Name[3:]
					servers.Lock()
					if s, ok := servers.Get(name); !ok {
						s = &Server{name: name}
						servers.Add(name, s)
						go func() {
							defer func() {
								if r := recover(); r != nil {
									err, ok := r.(error)
									if !ok {
										err = fmt.Errorf("pkg: %v", r)
									}
									fmt.Printf(err.Error())
								}
							}()
							if err := s.Start(); err != nil {
								panic(err)
							}
						}()
					} else {
						go func() {
							defer func() {
								if r := recover(); r != nil {
									err, ok := r.(error)
									if !ok {
										err = fmt.Errorf("pkg: %v", r)
									}
									fmt.Printf(err.Error())
								}
							}()
							if err := s.Start(); err != nil {
								panic(err)
							}
						}()
					}
					servers.Unlock()
				}
			case unix.RTM_DELLINK:
				//				if msg.Change == unix.IFF_UP {
				//				fmt.Printf("dellink %#+v\n", msg)
				servers.Lock()
				name := msg.Attrs().Name[3:]
				if s, ok := servers.Get(name); ok {
					go func() {
						defer func() {
							if r := recover(); r != nil {
								err, ok := r.(error)
								if !ok {
									err = fmt.Errorf("pkg: %v", r)
								}
								fmt.Printf(err.Error())
							}
						}()
						if err := s.Stop(true); err != nil {
							panic(err)
						}
					}()
				}
				servers.Unlock()
				//				}
			}
		}
	}

}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"
	"math"
	"os"
	"os/signal"
	"strings"
	"time"

	"github.com/bcurren/go-hue"
	"github.com/bitly/go-simplejson"
	"github.com/davecgh/go-spew/spew"
	// "github.com/ninjasphere/driver-go-hue/bulbmonitor"
	"github.com/ninjasphere/go-ninja"
)

var _ = fmt.Printf

type Light struct {
	Id            string
	Name          string
	Bus           *ninja.DeviceBus
	OnOffBus      *ninja.ChannelBus
	colorBus      *ninja.ChannelBus
	brightnessBus *ninja.ChannelBus
	Bridge        *hue.Bridge
	User          *hue.User
	LightState    *hue.LightState
	Batch         bool
	batchBus      *ninja.ChannelBus
	//bulbMonitor   *bulbmonitor.BulbMonitor
}

//Returns json state as defined by Ninja light protocol
func (l Light) GetJsonLightState() *simplejson.Json {
	st := l.LightState
	js := simplejson.New()
	js.Set("on", st.On)
	js.Set("bri", st.Brightness)
	js.Set("sat", st.Saturation)
	js.Set("hue", st.Hue)
	js.Set("ct", st.ColorTemp)
	js.Set("transitionTime", st.TransitionTime)
	js.Set("alert", st.Alert)
	js.Set("xy", st.XY)
	return js
}

func getBridge() *hue.Bridge {
	nobridge := true
	var allbridges []*hue.Bridge
	for nobridge {
		allbridges, _ = hue.FindBridges()
		if len(allbridges) == 0 {
			log.Printf("Couldn't find bridge, retrying")
			time.Sleep(time.Second * 2) //this sucks
		} else {
			nobridge = false
			log.Printf("got %d bridges: %s", len(allbridges), allbridges)
		}
	}
	return allbridges[0]
}

func getUser(bridge *hue.Bridge) *hue.User {
	var user *hue.User
	var err error
	noUser := true
	retries := 0
	serial, err := ninja.GetSerial()
	if err != nil {
		log.Fatalf("Problem determining if hue user is valid: %s", err)
	}
	username := serial + serial //username must be long 10-40 characters
	isvaliduser, err := bridge.IsValidUser(username)
	if err != nil {
		log.Printf("Problem determining if hue user is valid")
	}

	if isvaliduser {
		user = hue.NewUserWithBridge(username, bridge)
	} else {
		for noUser {
			user, err = bridge.CreateUser("ninjadevice", username)
			if err != nil {
				if strings.Contains(err.Error(), "101") { // there's probably a nicer way to check this
					retries++
					log.Printf("Couldn't make user, push link button. Retry: %d", retries)
					time.Sleep(time.Second * 2) //this sucks
				} else {
					log.Fatalf("Error creating user: %s", err)
				}
			}

			if user != nil {
				noUser = false
			}
		}
	}
	return user
}

func getOnOffBus(light *Light) *ninja.ChannelBus {
	methods := []string{"turnOn", "turnOff", "set"}
	events := []string{"state"}
	onOffBus, _ := light.Bus.AnnounceChannel("on-off", "on-off", methods, events, func(method string, payload *simplejson.Json) {
		if light.Batch == true {
			return
		}
		switch method {
		case "turnOn":
			light.turnOnOff(true)
		case "turnOff":
			light.refreshLightState()
			light.turnOnOff(false)
		case "set":
			state, _ := payload.GetIndex(0).Bool()
			log.Printf("Setting to %t!", state)
			light.turnOnOff(state)
		default:
			log.Printf("On-off got an unknown method %s", method)
			return
		}
	})

	return onOffBus
}

func getBrightBus(light *Light) *ninja.ChannelBus {
	methods := []string{"set"}
	events := []string{"state"}
	brightnessBus, _ := light.Bus.AnnounceChannel("brightness", "brightness", methods, events, func(method string, payload *simplejson.Json) {
		if light.Batch == true {
			return
		}

		switch method {
		case "set":
			brightness, _ := payload.GetIndex(0).Float64()
			log.Printf("Setting brightness to %f!", brightness)
			light.setBrightness(brightness)
		default:
			log.Printf("Brightness got an unknown method %s", method)
			return
		}

	})

	return brightnessBus

}

func getColorBus(light *Light) *ninja.ChannelBus {
	methods := []string{"set"}
	events := []string{"state"}
	colorBus, _ := light.Bus.AnnounceChannel("color", "color", methods, events, func(method string, payload *simplejson.Json) {
		if light.Batch == true {
			return
		}
		switch method {
		case "set":
			log.Printf("Setting color to %s!", payload)
			mode, err := payload.Get("mode").String()
			if err != nil {
				log.Printf("No mode sent to color bus: %s", err)
				spew.Dump(payload)
			}
			light.setColor(payload, mode)
		default:
			log.Printf("Color got an unknown method %s", method)
		}
	})

	return colorBus
}

func getBatchBus(light *Light) *ninja.ChannelBus {
	methods := []string{"setBatch"}
	events := []string{"state"}
	batchBus, _ := light.Bus.AnnounceChannel("core.batching", "core.batching", methods, events, func(method string, payload *simplejson.Json) {
		switch method {
		case "setBatch":
			light.setBatchColor(payload.GetIndex(0))
		default:
			log.Printf("Color got an unknown method %s", method)
			return
		}
	})

	return batchBus
}

func NewLight(l *hue.Light, bus *ninja.DriverBus, bridge *hue.Bridge, user *hue.User) (*Light, error) { //TODO cut this down!

	lightState := createLightState()

	light := &Light{
		Id:         l.Id,
		Name:       l.Name,
		Bridge:     bridge,
		User:       user,
		LightState: lightState,
		Batch:      false,
	}

	sigs, _ := simplejson.NewJson([]byte(`{
			"ninja:manufacturer": "Phillips",
			"ninja:productName": "Hue",
			"manufacturer:productModelId": "",
			"ninja:productType": "Light",
			"ninja:thingType": "light"
	}`))

	la, _ := user.GetLightAttributes(l.Id)
	sigs.Set("manufacturer:productModelId", la.ModelId)

	deviceBus, _ := bus.AnnounceDevice(l.Id, "hue", l.Name, sigs)
	light.Bus = deviceBus
	light.OnOffBus = getOnOffBus(light)
	light.brightnessBus = getBrightBus(light)
	light.colorBus = getColorBus(light)
	light.batchBus = getBatchBus(light)

	return light, nil
}

func (l Light) StartBatch() {
	l.Batch = true
}

func (l Light) EndBatch() {
	l.Batch = false
	l.User.SetLightState(l.Id, l.LightState)
	l.OnOffBus.SendEvent("state", l.GetJsonLightState())
	spew.Dump(l.LightState)
}

func (l Light) turnOnOff(state bool) {
	if !l.Batch {
		lightState := &hue.LightState{
			On: &state,
		}
		l.User.SetLightState(l.Id, lightState)

	}
}

func (l Light) setBrightness(fbrightness float64) {
	l.refreshLightState()
	brightness := uint8(fbrightness * math.MaxUint8)
	on := bool(true)
	l.LightState.Brightness = &brightness
	l.LightState.On = &on
	if !l.Batch {
		l.User.SetLightState(l.Id, l.LightState)
		l.brightnessBus.SendEvent("state", l.GetJsonLightState())
	}
}

func (l Light) setColor(payload *simplejson.Json, mode string) {
	l.refreshLightState()
	switch mode {
	case "hue": //TODO less verbose plz
		if trans, e := payload.Get("transition").Int(); e == nil {
			l.setTransition(trans)
		}
		fhue, _ := payload.Get("hue").Float64()
		hue := uint16(fhue * math.MaxUint16)
		fsaturation, _ := payload.Get("saturation").Float64()
		saturation := uint8(fsaturation * math.MaxUint8)
		on := bool(true)
		l.LightState.Hue = &hue
		l.LightState.Saturation = &saturation
		l.LightState.On = &on
		l.LightState.XY = nil
		l.LightState.ColorTemp = nil
	case "xy":
		if trans, e := payload.Get("transition").Int(); e == nil {
			l.setTransition(trans)
		}
		x, _ := payload.Get("x").Float64()
		y, _ := payload.Get("y").Float64()
		xy := []float64{x, y}
		l.LightState.XY = xy
		l.LightState.Hue = nil
		l.LightState.Saturation = nil
		l.LightState.ColorTemp = nil
	case "temperature":
		if trans, e := payload.Get("transition").Int(); e == nil {
			l.setTransition(trans)
		}
		temp, _ := payload.Get("temperature").Float64()
		utemp := uint16(math.Floor(1000000 / temp))
		l.LightState.ColorTemp = &utemp
		l.LightState.XY = nil
		l.LightState.Hue = nil
		l.LightState.Saturation = nil
	default:
		log.Printf("Bad color mode: %s", mode)
		return
	}

	if !l.Batch {
		l.User.SetLightState(l.Id, l.LightState)
		l.colorBus.SendEvent("state", l.GetJsonLightState())
	}

}

func (l Light) setTransition(transTime int) {
	transTime = transTime / 10 //HUE API uses 1/10th of a second
	utranstime := uint16(transTime)
	l.LightState.TransitionTime = &utranstime
}

func (l Light) setBatchColor(payload *simplejson.Json) { //TODO This will set each param individually. Better to build full state then send to bulb.
	l.StartBatch()

	color := payload.Get("color")
	if color != nil {
		l.setColor(color, "hue")
		prettycolor, _ := color.EncodePretty()
		log.Printf("Got color: %s", prettycolor)
	}

	if brightness, err := payload.Get("brightness").Float64(); err == nil {
		log.Printf("Got brightness: %f", brightness)
		l.setBrightness(brightness)
	}

	if onoff, err := payload.Get("on-off").Bool(); err == nil {
		log.Printf("Got onoff: %t", onoff)
		l.turnOnOff(onoff)
	}

	if transition, err := payload.Get("transition").Int(); err == nil {
		log.Printf("Got transition: %d", transition)
		l.setTransition(transition)
	}

	l.EndBatch()
}

func createLightState() *hue.LightState {

	on := bool(false)
	brightness := uint8(0)
	saturation := uint8(0)
	hueVal := uint16(0)
	transitionTime := uint16(0)
	alert := ""
	temp := uint16(0)
	xy := []float64{0, 0}

	lightState := &hue.LightState{
		On:             &on,
		Brightness:     &brightness,
		Saturation:     &saturation,
		Hue:            &hueVal,
		ColorTemp:      &temp,
		TransitionTime: &transitionTime,
		Alert:          alert,
		XY:             xy,
	}

	return lightState
}

func getCurDir() string {
	pwd, _ := os.Getwd()
	return pwd + "/"
}

func (l Light) sendLightState() {

	l.User.SetLightState(l.Id, l.LightState)
	l.OnOffBus.SendEvent("state", l.GetJsonLightState())
}

func (l Light) refreshLightState() { //TOOD fix verboseness
	newstate, _ := l.User.GetLightAttributes(l.Id)
	bulbstate := newstate.State
	mybulbstate := l.LightState
	transtime := uint16(5) //TODO: Remove when web is sending sensible value
	// l.LightState = newstate.State //TODO why doesnt this work?

	mybulbstate.On = bulbstate.On
	mybulbstate.Brightness = bulbstate.Brightness
	mybulbstate.Hue = bulbstate.Hue
	mybulbstate.Saturation = bulbstate.Saturation
	mybulbstate.XY = bulbstate.XY
	mybulbstate.ColorTemp = bulbstate.ColorTemp
	mybulbstate.Alert = bulbstate.Alert
	mybulbstate.Effect = bulbstate.Effect
	mybulbstate.TransitionTime = &transtime

}

func printState(s *hue.LightState) {
	spew.Dump(s)
}

func main() {

	conn, err := ninja.Connect("com.ninjablocks.hue")
	if err != nil {
		log.Fatalf("Could not connect to MQTT: %s", err)
	}

	bus, err := conn.AnnounceDriver("com.ninjablocks.hue", "driver-hue", getCurDir())
	if err != nil {
		log.Fatalf("Could not get driver bus: %s", err)
	}

	bridge := getBridge()
	user := getUser(bridge)

	allLights, err := user.GetLights()
	if err != nil {
		log.Fatalf("Couldn't get lights:  %s", err)
	}

	for _, l := range allLights {
		_, err := NewLight(&l, bus, bridge, user)
		if err != nil {
			log.Fatalf("Error creating light instance:  %s", err)
		}

	}

	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt, os.Kill)

	// Block until a signal is received.
	s := <-c
	fmt.Println("Got signal:", s)

}

// func verifyState (sent *simpleJson) bool { TODO
//if sent state == current state, return true
//else return false
// }
<commit_msg>batching fully working in hue driver<commit_after>package main

import (
	"fmt"
	"log"
	"math"
	"os"
	"os/signal"
	"strings"
	"time"

	"github.com/bcurren/go-hue"
	"github.com/bitly/go-simplejson"
	"github.com/davecgh/go-spew/spew"
	"github.com/ninjasphere/go-ninja"
)

var _ = fmt.Printf

type Light struct {
	Id            string
	Name          string
	Bus           *ninja.DeviceBus
	OnOffBus      *ninja.ChannelBus
	colorBus      *ninja.ChannelBus
	brightnessBus *ninja.ChannelBus
	Bridge        *hue.Bridge
	User          *hue.User
	LightState    *hue.LightState
	Batch         bool
	batchBus      *ninja.ChannelBus
	//bulbMonitor   *bulbmonitor.BulbMonitor
}

//--------------------------------------------------[HUE BASESTATION]----------------------------------------------------------------------

func getBridge() *hue.Bridge {
	nobridge := true
	var allbridges []*hue.Bridge
	for nobridge {
		allbridges, _ = hue.FindBridges()
		if len(allbridges) == 0 {
			log.Printf("Couldn't find bridge, retrying")
			time.Sleep(time.Second * 2) //this sucks
		} else {
			nobridge = false
			log.Printf("got %d bridges: %s", len(allbridges), allbridges)
		}
	}
	return allbridges[0]
}

func getUser(bridge *hue.Bridge) *hue.User {
	var user *hue.User
	var err error
	noUser := true
	retries := 0
	serial, err := ninja.GetSerial()
	if err != nil {
		log.Fatalf("Problem determining if hue user is valid: %s", err)
	}
	username := serial + serial //username must be long 10-40 characters
	isvaliduser, err := bridge.IsValidUser(username)
	if err != nil {
		log.Printf("Problem determining if hue user is valid")
	}

	if isvaliduser {
		user = hue.NewUserWithBridge(username, bridge)
	} else {
		for noUser {
			user, err = bridge.CreateUser("ninjadevice", username)
			if err != nil {
				if strings.Contains(err.Error(), "101") { // there's probably a nicer way to check this
					retries++
					log.Printf("Couldn't make user, push link button. Retry: %d", retries)
					time.Sleep(time.Second * 2) //this sucks
				} else {
					log.Fatalf("Error creating user: %s", err)
				}
			}

			if user != nil {
				noUser = false
			}
		}
	}
	return user
}

//--------------------------------------------------[BUSES]----------------------------------------------------------------------

func getOnOffBus(light *Light) *ninja.ChannelBus {
	methods := []string{"turnOn", "turnOff", "set"}
	events := []string{"state"}
	onOffBus, _ := light.Bus.AnnounceChannel("on-off", "on-off", methods, events, func(method string, payload *simplejson.Json) {
		if light.Batch == true {
			return
		}
		switch method {
		case "turnOn":
			light.sendLightState(light.turnOnOff(true))
		case "turnOff":
			light.sendLightState(light.turnOnOff(false))
		case "set":
			state, _ := payload.GetIndex(0).Bool()
			light.sendLightState(light.turnOnOff(state))
		default:
			log.Printf("On-off got an unknown method %s", method)
			return
		}
	})

	return onOffBus
}

func getBrightBus(light *Light) *ninja.ChannelBus {
	methods := []string{"set"}
	events := []string{"state"}
	brightnessBus, _ := light.Bus.AnnounceChannel("brightness", "brightness", methods, events, func(method string, payload *simplejson.Json) {
		if light.Batch == true {
			return
		}

		switch method {
		case "set":
			brightness, _ := payload.GetIndex(0).Float64()
			log.Printf("Setting brightness to %f!", brightness)
			light.sendLightState(light.setBrightness(brightness))
		default:
			log.Printf("Brightness got an unknown method %s", method)
			return
		}

	})

	return brightnessBus

}

func getColorBus(light *Light) *ninja.ChannelBus {
	methods := []string{"set"}
	events := []string{"state"}
	colorBus, _ := light.Bus.AnnounceChannel("color", "color", methods, events, func(method string, payload *simplejson.Json) {
		if light.Batch == true {
			return
		}
		switch method {
		case "set":
			log.Printf("Setting color to %s!", payload)
			light.sendLightState(light.setColor(payload))
		default:
			log.Printf("Color got an unknown method %s", method)
		}
	})

	return colorBus
}

func getBatchBus(light *Light) *ninja.ChannelBus {
	methods := []string{"setBatch"}
	events := []string{"state"}
	batchBus, _ := light.Bus.AnnounceChannel("core.batching", "core.batching", methods, events, func(method string, payload *simplejson.Json) {
		switch method {
		case "setBatch":
			light.setBatchColor(payload.GetIndex(0))
		default:
			log.Printf("Color got an unknown method %s", method)
			return
		}
	})

	return batchBus
}

func NewLight(l *hue.Light, bus *ninja.DriverBus, bridge *hue.Bridge, user *hue.User) (*Light, error) { //TODO cut this down!

	lightState := createLightState()

	light := &Light{
		Id:         l.Id,
		Name:       l.Name,
		Bridge:     bridge,
		User:       user,
		LightState: lightState,
		Batch:      false,
	}

	sigs, _ := simplejson.NewJson([]byte(`{
			"ninja:manufacturer": "Phillips",
			"ninja:productName": "Hue",
			"manufacturer:productModelId": "",
			"ninja:productType": "Light",
			"ninja:thingType": "light"
	}`))

	la, _ := user.GetLightAttributes(l.Id)
	sigs.Set("manufacturer:productModelId", la.ModelId)

	deviceBus, _ := bus.AnnounceDevice(l.Id, "hue", l.Name, sigs)
	light.Bus = deviceBus
	light.OnOffBus = getOnOffBus(light)
	light.brightnessBus = getBrightBus(light)
	light.colorBus = getColorBus(light)
	light.batchBus = getBatchBus(light)

	return light, nil
}

func (l Light) turnOnOff(state bool) *hue.LightState {
	lightState := &hue.LightState{
		On: &state,
	}
	return lightState

}

func (l Light) setBrightness(fbrightness float64) *hue.LightState {
	brightness := uint8(fbrightness * math.MaxUint8)
	lightState := &hue.LightState{
		Brightness: &brightness,
	}
	return lightState
}

func (l Light) setColor(payload *simplejson.Json) *hue.LightState {

	var lightState *hue.LightState

	mode, err := payload.Get("color").Get("mode").String()
	if err != nil {
		log.Printf("No mode sent to color bus: %s", err)
		spew.Dump(payload)
	}

	switch mode {
	case "hue": //TODO less verbose plz
		fhue, _ := payload.Get("hue").Float64()
		uint16hue := uint16(fhue * math.MaxUint16)
		fsaturation, _ := payload.Get("saturation").Float64()
		saturation := uint8(fsaturation * math.MaxUint8)
		lightState = &hue.LightState{
			Hue:        &uint16hue,
			Saturation: &saturation,
		}

	case "xy":
		x, _ := payload.Get("x").Float64()
		y, _ := payload.Get("y").Float64()
		xy := []float64{x, y}
		lightState = &hue.LightState{
			XY: xy,
		}
	case "temperature":
		temp, _ := payload.Get("temperature").Float64()
		utemp := uint16(math.Floor(1000000 / temp))
		lightState = &hue.LightState{
			ColorTemp: &utemp,
		}
	default:
		log.Printf("Bad color mode: %s", mode)
		return nil
	}

	if trans, e := payload.Get("transition").Int(); e == nil {
		st := l.setTransition(trans)
		lightState.TransitionTime = st.TransitionTime
	}

	return lightState

}

func (l Light) setTransition(transTime int) *hue.LightState {
	transTime = transTime / 10 //HUE API uses 1/10th of a second
	utranstime := uint16(transTime)
	newstate := &hue.LightState{
		TransitionTime: &utranstime,
	}
	return newstate
}

func (l Light) setBatchColor(payload *simplejson.Json) {
	var newlightstate *hue.LightState

	color := payload.Get("color")
	if color != nil {
		newlightstate = l.setColor(payload)
	}

	if brightness, err := payload.Get("brightness").Float64(); err == nil {
		bri := l.setBrightness(brightness)
		newlightstate.Brightness = bri.Brightness
	}

	if onoff, err := payload.Get("on-off").Bool(); err == nil {
		st := l.turnOnOff(onoff)
		newlightstate.On = st.On
	}

	if transition, err := payload.Get("transition").Int(); err == nil {
		trans := l.setTransition(transition)
		newlightstate.TransitionTime = trans.TransitionTime
	}

	l.sendLightState(newlightstate)

}

func createLightState() *hue.LightState {

	on := bool(false)
	brightness := uint8(0)
	saturation := uint8(0)
	hueVal := uint16(0)
	transitionTime := uint16(0)
	alert := ""
	temp := uint16(0)
	xy := []float64{0, 0}

	lightState := &hue.LightState{
		On:             &on,
		Brightness:     &brightness,
		Saturation:     &saturation,
		Hue:            &hueVal,
		ColorTemp:      &temp,
		TransitionTime: &transitionTime,
		Alert:          alert,
		XY:             xy,
	}

	return lightState
}

func getCurDir() string {
	pwd, _ := os.Getwd()
	return pwd + "/"
}

func (l Light) sendLightState(newstate *hue.LightState) {
	l.User.SetLightState(l.Id, newstate)
	l.OnOffBus.SendEvent("state", l.GetJsonLightState())
}

func (l Light) refreshLightState() { //TOOD fix verboseness
	newstate, _ := l.User.GetLightAttributes(l.Id)
	bulbstate := newstate.State
	mybulbstate := l.LightState
	transtime := uint16(5) //TODO: Remove when web is sending sensible value
	// l.LightState = newstate.State //TODO why doesnt this work?

	mybulbstate.On = bulbstate.On
	mybulbstate.Brightness = bulbstate.Brightness
	mybulbstate.Hue = bulbstate.Hue
	mybulbstate.Saturation = bulbstate.Saturation
	mybulbstate.XY = bulbstate.XY
	mybulbstate.ColorTemp = bulbstate.ColorTemp
	mybulbstate.Alert = bulbstate.Alert
	mybulbstate.Effect = bulbstate.Effect
	mybulbstate.TransitionTime = &transtime

}

//Returns json state as defined by Ninja light protocol
func (l Light) GetJsonLightState() *simplejson.Json {
	st := l.LightState
	js := simplejson.New()
	js.Set("on", st.On)
	js.Set("bri", st.Brightness)
	js.Set("sat", st.Saturation)
	js.Set("hue", st.Hue)
	js.Set("ct", st.ColorTemp)
	js.Set("transitionTime", st.TransitionTime)
	js.Set("alert", st.Alert)
	js.Set("xy", st.XY)
	return js
}

func printState(s *hue.LightState) {
	spew.Dump(s)
}

func main() {

	conn, err := ninja.Connect("com.ninjablocks.hue")
	if err != nil {
		log.Fatalf("Could not connect to MQTT: %s", err)
	}

	bus, err := conn.AnnounceDriver("com.ninjablocks.hue", "driver-hue", getCurDir())
	if err != nil {
		log.Fatalf("Could not get driver bus: %s", err)
	}

	bridge := getBridge()
	user := getUser(bridge)

	allLights, err := user.GetLights()
	if err != nil {
		log.Fatalf("Couldn't get lights:  %s", err)
	}

	for _, l := range allLights {
		_, err := NewLight(&l, bus, bridge, user)
		if err != nil {
			log.Fatalf("Error creating light instance:  %s", err)
		}

	}

	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt, os.Kill)

	// Block until a signal is received.
	s := <-c
	fmt.Println("Got signal:", s)

}

// func verifyState (sent *simpleJson) bool { TODO
//if sent state == current state, return true
//else return false
// }
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"io/ioutil"
	"math/big"
	"os"
	"sort"
	"strconv"
	"strings"
	"time"
	"unicode/utf8"
)

func main() {
	var fromStr string
	flag.StringVar(&fromStr, "from", "0-1-1", "from date")
	var toStr string
	flag.StringVar(&toStr, "to", "9999-1-1", "to date")
	flag.Parse()

	// options
	parseDate := func(str string) time.Time {
		parts := dateSepPattern.Split(str, -1)
		if len(parts) != 3 {
			panic(me(nil, "bad date: %s", str))
		}
		year, err := strconv.Atoi(parts[0])
		ce(err, "parse year: %s", str)
		month, err := strconv.Atoi(parts[1])
		ce(err, "parse month: %s", str)
		day, err := strconv.Atoi(parts[2])
		ce(err, "parse day: %s", str)
		return time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.Local)
	}
	fromTime := parseDate(fromStr).Add(-time.Hour)
	toTime := parseDate(toStr).Add(time.Hour)

	// usage
	args := flag.Args()
	if len(args) < 1 {
		pt("usage: %s [options] <file path>\n", os.Args[0])
		flag.Usage()
		return
	}

	// read ledger file
	ledgerPath := args[0]
	contentBytes, err := ioutil.ReadFile(ledgerPath)
	ce(err, "read ledger")
	content := string(contentBytes)
	content = strings.Replace(content, "\r\n", "\n", -1)
	content = strings.Replace(content, "\r", "\n", -1)

	// parse blocks
	var blocks [][]string
	var block []string
	for _, line := range strings.Split(content, "\n") {
		line = strings.TrimSpace(line)
		if len(line) == 0 {
			if len(block) > 0 {
				blocks = append(blocks, block)
				block = []string{}
			}
		} else {
			block = append(block, line)
		}
	}
	if len(block) > 0 {
		blocks = append(blocks, block)
	}

	// transaction
	type Account struct {
		Name     string
		Subs     map[string]*Account
		Parent   *Account
		Balances map[string]*big.Rat
	}
	type Entry struct {
		Account     *Account
		Currency    string
		Amount      *big.Rat
		Description string
	}
	type Transaction struct {
		Year        int
		Month       int
		Day         int
		Description string
		Entries     []*Entry
	}
	var transactions []*Transaction

	rootAccount := &Account{
		Name:     "root",
		Subs:     make(map[string]*Account),
		Parent:   nil,
		Balances: make(map[string]*big.Rat),
	}
	var getAccount func(root *Account, path []string) *Account
	getAccount = func(root *Account, path []string) *Account {
		if len(path) == 0 {
			panic(me(nil, "bad account: %v", path))
		} else if len(path) == 1 {
			name := path[0]
			account, ok := root.Subs[name]
			if ok {
				return account
			}
			account = &Account{
				Name:     name,
				Subs:     make(map[string]*Account),
				Parent:   root,
				Balances: make(map[string]*big.Rat),
			}
			root.Subs[name] = account
			return account
		}
		name := path[0]
		account, ok := root.Subs[name]
		if !ok {
			account = &Account{
				Name:     name,
				Subs:     make(map[string]*Account),
				Parent:   root,
				Balances: make(map[string]*big.Rat),
			}
			root.Subs[name] = account
		}
		return getAccount(account, path[1:])
	}

	// collect transactions
	for _, block := range blocks {
		n := 0
		transaction := new(Transaction)

		// parse
		for _, line := range block {
			if strings.HasPrefix(line, "#") {
				continue
			}
			n++

			if n == 1 {
				// transaction header
				parts := blanksPattern.Split(line, 2)
				if len(parts) != 2 {
					panic(me(nil, "bad transaction header: %s", line))
				}

				dateStr := parts[0]
				dateParts := dateSepPattern.Split(dateStr, -1)
				if len(dateParts) != 3 {
					panic(me(nil, "bad date: %s", line))
				}
				year, err := strconv.Atoi(dateParts[0])
				ce(err, "parse year: %s", line)
				transaction.Year = year
				month, err := strconv.Atoi(dateParts[1])
				ce(err, "parse month: %s", line)
				transaction.Month = month
				day, err := strconv.Atoi(dateParts[2])
				ce(err, "parse day: %s", line)
				transaction.Day = day

				transaction.Description = parts[1]

			} else {
				// entry
				parts := blanksPattern.Split(line, 3)
				if len(parts) < 2 {
					panic(me(nil, "bad entry: %s", line))
				}
				entry := new(Entry)

				accountStr := parts[0]
				account := getAccount(rootAccount, strings.Split(accountStr, "："))
				entry.Account = account

				currency, runeSize := utf8.DecodeRuneInString(parts[1])
				entry.Currency = string(currency)
				amountStr := parts[1][runeSize:]
				amount := new(big.Rat)
				_, ok := amount.SetString(amountStr)
				if !ok {
					panic(me(nil, "bad amount: %s", amountStr))
				}
				entry.Amount = amount

				if len(parts) > 2 {
					entry.Description = parts[2]
				}

				transaction.Entries = append(transaction.Entries, entry)
			}

		}

		if transaction.Year == 0 {
			// empty
			continue
		}
		t := time.Date(transaction.Year, time.Month(transaction.Month), transaction.Day,
			0, 0, 0, 0, time.Local)
		if t.Before(fromTime) || t.After(toTime) {
			// out of range
			continue
		}

		// check balance
		sum := big.NewRat(0, 1)
		for _, entry := range transaction.Entries {
			sum.Add(sum, entry.Amount)
			// update account balance
			account := entry.Account
			for account != nil {
				balance, ok := account.Balances[entry.Currency]
				if !ok {
					balance = big.NewRat(0, 1)
					account.Balances[entry.Currency] = balance
				}
				balance.Add(balance, entry.Amount)
				account = account.Parent
			}
		}
		if !(sum.Cmp(zeroRat) == 0) {
			panic(me(nil, "not balanced: %s", strings.Join(block, "\n")))
		}

		transactions = append(transactions, transaction)
	}

	// print accounts
	var printAccount func(account *Account, level int)
	printAccount = func(account *Account, level int) {
		allZero := true
		for _, balance := range account.Balances {
			if balance.Cmp(zeroRat) != 0 {
				allZero = false
				break
			}
		}
		if allZero && account != rootAccount {
			return
		}
		pt("%s%s", strings.Repeat(" │    ", level), account.Name)
		var currencyNames []string
		for name := range account.Balances {
			currencyNames = append(currencyNames, name)
		}
		sort.Strings(currencyNames)
		for _, name := range currencyNames {
			balance := account.Balances[name]
			pt(" %s%s", name, balance.FloatString(2))
		}
		pt("\n")

		var subNames []string
		for name := range account.Subs {
			subNames = append(subNames, name)
		}
		sort.Strings(subNames)
		for _, name := range subNames {
			printAccount(account.Subs[name], level+1)
		}
	}
	var subNames []string
	for name := range rootAccount.Subs {
		subNames = append(subNames, name)
	}
	sort.Strings(subNames)
	for _, name := range subNames {
		printAccount(rootAccount.Subs[name], 0)
	}

}
<commit_msg>add props subcommand<commit_after>package main

import (
	"flag"
	"io/ioutil"
	"math/big"
	"os"
	"sort"
	"strconv"
	"strings"
	"time"
	"unicode/utf8"
)

func main() {
	var fromStr string
	flag.StringVar(&fromStr, "from", "0-1-1", "from date")
	var toStr string
	flag.StringVar(&toStr, "to", "9999-1-1", "to date")
	var cmdProperties bool
	flag.BoolVar(&cmdProperties, "props", false, "show properties")

	flag.Parse()

	// options
	parseDate := func(str string) time.Time {
		parts := dateSepPattern.Split(str, -1)
		if len(parts) != 3 {
			panic(me(nil, "bad date: %s", str))
		}
		year, err := strconv.Atoi(parts[0])
		ce(err, "parse year: %s", str)
		month, err := strconv.Atoi(parts[1])
		ce(err, "parse month: %s", str)
		day, err := strconv.Atoi(parts[2])
		ce(err, "parse day: %s", str)
		return time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.Local)
	}
	fromTime := parseDate(fromStr).Add(-time.Hour)
	toTime := parseDate(toStr).Add(time.Hour)

	// usage
	args := flag.Args()
	if len(args) < 1 {
		pt("usage: %s [options] <file path>\n", os.Args[0])
		flag.Usage()
		return
	}

	// read ledger file
	ledgerPath := args[0]
	contentBytes, err := ioutil.ReadFile(ledgerPath)
	ce(err, "read ledger")
	content := string(contentBytes)
	content = strings.Replace(content, "\r\n", "\n", -1)
	content = strings.Replace(content, "\r", "\n", -1)

	// parse blocks
	var blocks [][]string
	var block []string
	for _, line := range strings.Split(content, "\n") {
		line = strings.TrimSpace(line)
		if len(line) == 0 {
			if len(block) > 0 {
				blocks = append(blocks, block)
				block = []string{}
			}
		} else {
			block = append(block, line)
		}
	}
	if len(block) > 0 {
		blocks = append(blocks, block)
	}

	// transaction
	type Account struct {
		Name     string
		Subs     map[string]*Account
		Parent   *Account
		Balances map[string]*big.Rat
	}
	type Entry struct {
		Account     *Account
		Currency    string
		Amount      *big.Rat
		Description string
	}
	type Transaction struct {
		Year        int
		Month       int
		Day         int
		Time        time.Time
		Description string
		Entries     []*Entry
	}
	var transactions []*Transaction

	rootAccount := &Account{
		Name:     "root",
		Subs:     make(map[string]*Account),
		Parent:   nil,
		Balances: make(map[string]*big.Rat),
	}
	var getAccount func(root *Account, path []string) *Account
	getAccount = func(root *Account, path []string) *Account {
		if len(path) == 0 {
			panic(me(nil, "bad account: %v", path))
		} else if len(path) == 1 {
			name := path[0]
			account, ok := root.Subs[name]
			if ok {
				return account
			}
			account = &Account{
				Name:     name,
				Subs:     make(map[string]*Account),
				Parent:   root,
				Balances: make(map[string]*big.Rat),
			}
			root.Subs[name] = account
			return account
		}
		name := path[0]
		account, ok := root.Subs[name]
		if !ok {
			account = &Account{
				Name:     name,
				Subs:     make(map[string]*Account),
				Parent:   root,
				Balances: make(map[string]*big.Rat),
			}
			root.Subs[name] = account
		}
		return getAccount(account, path[1:])
	}

	// collect transactions
	for _, block := range blocks {
		n := 0
		transaction := new(Transaction)

		// parse
		for _, line := range block {
			if strings.HasPrefix(line, "#") {
				continue
			}
			n++

			if n == 1 {
				// transaction header
				parts := blanksPattern.Split(line, 2)
				if len(parts) != 2 {
					panic(me(nil, "bad transaction header: %s", line))
				}

				dateStr := parts[0]
				dateParts := dateSepPattern.Split(dateStr, -1)
				if len(dateParts) != 3 {
					panic(me(nil, "bad date: %s", line))
				}
				year, err := strconv.Atoi(dateParts[0])
				ce(err, "parse year: %s", line)
				transaction.Year = year
				month, err := strconv.Atoi(dateParts[1])
				ce(err, "parse month: %s", line)
				transaction.Month = month
				day, err := strconv.Atoi(dateParts[2])
				ce(err, "parse day: %s", line)
				transaction.Day = day
				transaction.Time = time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.Local)

				transaction.Description = parts[1]

			} else {
				// entry
				parts := blanksPattern.Split(line, 3)
				if len(parts) < 2 {
					panic(me(nil, "bad entry: %s", line))
				}
				entry := new(Entry)

				accountStr := parts[0]
				account := getAccount(rootAccount, strings.Split(accountStr, "："))
				entry.Account = account

				currency, runeSize := utf8.DecodeRuneInString(parts[1])
				entry.Currency = string(currency)
				amountStr := parts[1][runeSize:]
				amount := new(big.Rat)
				_, ok := amount.SetString(amountStr)
				if !ok {
					panic(me(nil, "bad amount: %s", amountStr))
				}
				entry.Amount = amount

				if len(parts) > 2 {
					entry.Description = parts[2]
				}

				transaction.Entries = append(transaction.Entries, entry)
			}

		}

		if transaction.Year == 0 {
			// empty
			continue
		}
		t := time.Date(transaction.Year, time.Month(transaction.Month), transaction.Day,
			0, 0, 0, 0, time.Local)
		if t.Before(fromTime) || t.After(toTime) {
			// out of range
			continue
		}

		// check balance
		sum := big.NewRat(0, 1)
		for _, entry := range transaction.Entries {
			sum.Add(sum, entry.Amount)
			// update account balance
			account := entry.Account
			for account != nil {
				balance, ok := account.Balances[entry.Currency]
				if !ok {
					balance = big.NewRat(0, 1)
					account.Balances[entry.Currency] = balance
				}
				balance.Add(balance, entry.Amount)
				account = account.Parent
			}
		}
		if !(sum.Cmp(zeroRat) == 0) {
			panic(me(nil, "not balanced: %s", strings.Join(block, "\n")))
		}

		transactions = append(transactions, transaction)
	}

	// print accounts
	var printAccount func(account *Account, level int)
	printAccount = func(account *Account, level int) {
		allZero := true
		for _, balance := range account.Balances {
			if balance.Cmp(zeroRat) != 0 {
				allZero = false
				break
			}
		}
		if allZero && account != rootAccount {
			return
		}
		pt("%s%s", strings.Repeat(" │    ", level), account.Name)
		var currencyNames []string
		for name := range account.Balances {
			currencyNames = append(currencyNames, name)
		}
		sort.Strings(currencyNames)
		for _, name := range currencyNames {
			balance := account.Balances[name]
			pt(" %s%s", name, balance.FloatString(2))
		}
		pt("\n")

		var subNames []string
		for name := range account.Subs {
			subNames = append(subNames, name)
		}
		sort.Strings(subNames)
		for _, name := range subNames {
			printAccount(account.Subs[name], level+1)
		}
	}
	printAccount(rootAccount, 0)

	if cmdProperties {
		accountNames := map[string]bool{
			"书籍":   true,
			"保健品":  true,
			"性用品":  true,
			"消耗品":  true,
			"物品":   true,
			"电子":   true,
			"药物":   true,
			"衣物服饰": true,
		}
		accounts := map[*Account]bool{}
		for name, account := range rootAccount.Subs["支出"].Subs {
			if accountNames[name] {
				accounts[account] = true
			}
		}
		var ts []*Transaction
	loop_t:
		for _, t := range transactions {
			for _, entry := range t.Entries {
				if accounts[entry.Account] {
					ts = append(ts, t)
					continue loop_t
				}
			}
		}
		sort.Slice(ts, func(i, j int) bool {
			return ts[i].Time.Before(ts[j].Time)
		})
		for _, t := range ts {
			pt("%s %s\n", t.Time.Format("01-02"), t.Description)
		}
	}

}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"flag"
	"fmt"
	"html/template"
	"log"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"runtime/debug"
	"strconv"
	"syscall"
)

var (
	templates *template.Template
	blaster   *Blaster

	flag_R        = flag.Uint("r", 17, "Red GPIO pin")
	flag_G        = flag.Uint("g", 22, "Green GPIO pin")
	flag_B        = flag.Uint("b", 27, "Blue GPIO pin")
	flag_Cooldown = flag.Uint("cooldown", 10, "Milliseconds cooldown between requests")
)

type RGB struct {
	R uint8
	G uint8
	B uint8
}

func mustParseTemplates() {
	var err error
	templates, err = template.ParseGlob("templates/*.html")

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

func attemptPiBlasterStart() error {
	cmd := exec.Command("pi-blaster")
	return cmd.Run()
}

func mustOpenPiBlaster() *os.File {
	file, err := os.OpenFile("/dev/pi-blaster", os.O_RDWR, os.ModeNamedPipe)

	if err != nil {
		if perr, ok := err.(*os.PathError); ok && perr.Err == syscall.ENOENT {
			err = attemptPiBlasterStart()
		}
	}

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

	return file
}

type Blaster struct {
	pipe  *os.File
	Input chan RGB
	Color chan func(RGB)
	shake chan struct{}

	r, g, b uint8
}

func NewBlaster() *Blaster {
	return &Blaster{
		Input: make(chan RGB),
		Color: make(chan func(RGB)),
		shake: make(chan struct{}),
	}
}

func (b *Blaster) Run() {
	b.pipe = mustOpenPiBlaster()
	defer b.pipe.Close()

	for {
		select {
		case c := <-b.Input:
			b.setAll(c)
		case c := <-b.Color:
			c(RGB{b.r, b.g, b.b})
		}
	}
}

func (b *Blaster) setPin(pin uint, val float64) error {
	chanCmd := fmt.Sprintf("%d=%f\n", pin, val)

	b.pipe.Write([]byte(chanCmd))
	return nil
}

func (b *Blaster) setChannelInteger(pin uint, val uint8) error {
	switch {
	case val > 255:
		return errors.New("can't go over 255. sorry mate.")
	case val < 0:
		return errors.New("can't go below 0. sorry mate.")
	default:
		fval := float64(val) / 255.0
		return b.setPin(pin, fval)
	}
}

func (b *Blaster) setRed(val uint8) error {
	return b.setChannelInteger(*flag_R, val)
}

func (b *Blaster) setGreen(val uint8) error {
	return b.setChannelInteger(*flag_G, val)
}

func (b *Blaster) setBlue(val uint8) error {
	return b.setChannelInteger(*flag_B, val)
}

type setterFunc func(v uint8) error

func (b *Blaster) setAll(c RGB) {
	b.setRed(c.R)
	b.setGreen(c.G)
	b.setBlue(c.B)
}

func errorHandler(w http.ResponseWriter, r *http.Request) {
	if err := recover(); err != nil {
		w.WriteHeader(401)

		fmt.Fprintf(w, "Oh...:(\n\n")

		if e, ok := err.(error); ok {
			w.Write([]byte(e.Error()))
			w.Write([]byte{'\n', '\n'})
			w.Write(debug.Stack())
		} else {
			fmt.Fprintf(w, "%s\n\n", err)
		}

		log.Println(
			"panic catched:", err,
			"\nRequest data:", r)
	}
}

func parseUint8OrZero(s string) uint8 {
	i, err := strconv.ParseUint(s, 10, 8)
	if err != nil {
		return 0
	}
	return uint8(i)
}

func actionHandler(w http.ResponseWriter, r *http.Request) {
	defer errorHandler(w, r)

	values, err := url.ParseQuery(r.URL.RawQuery)

	if err != nil {
		panic(err)
	}

	action := values.Get("action")

	switch action {
	case "lighter":
		blaster.Color <- func(c RGB) {
			blaster.Input <- RGB{c.R + 10, c.G + 10, c.B + 10}
		}
	case "darker":
		blaster.Color <- func(c RGB) {
			blaster.Input <- RGB{c.R - 10, c.G - 10, c.B - 10}
		}
	case "off":
		blaster.Input <- RGB{0, 0, 0}
	case "set":
		r := parseUint8OrZero(values.Get("r"))
		g := parseUint8OrZero(values.Get("g"))
		b := parseUint8OrZero(values.Get("b"))

		blaster.Input <- RGB{r, g, b}
	}
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
	defer errorHandler(w, r)
	templates.ExecuteTemplate(w, "index.html", nil)
}

func main() {
	flag.Parse()

	blaster = NewBlaster()
	go blaster.Run()

	blaster.Input <- RGB{}

	mustParseTemplates()

	http.HandleFunc("/", indexHandler)
	http.HandleFunc("/do", actionHandler)
	http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("templates"))))

	log.Fatal(http.ListenAndServe(":1337", nil))
}
<commit_msg>Add current color reporting<commit_after>package main

import (
	"errors"
	"flag"
	"fmt"
	"html/template"
	"log"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"runtime/debug"
	"strconv"
	"syscall"
)

var (
	templates *template.Template
	blaster   *Blaster

	flag_R        = flag.Uint("r", 17, "Red GPIO pin")
	flag_G        = flag.Uint("g", 22, "Green GPIO pin")
	flag_B        = flag.Uint("b", 27, "Blue GPIO pin")
	flag_Cooldown = flag.Uint("cooldown", 10, "Milliseconds cooldown between requests")
)

type RGB struct {
	R uint8
	G uint8
	B uint8
}

func (c RGB) String() string {
	return fmt.Sprintf("#%02x%02x%02x", c.R, c.G, c.B)
}

func mustParseTemplates() {
	var err error
	templates, err = template.ParseGlob("templates/*.html")

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

func attemptPiBlasterStart() error {
	cmd := exec.Command("pi-blaster")
	return cmd.Run()
}

func mustOpenPiBlaster() *os.File {
	file, err := os.OpenFile("/dev/pi-blaster", os.O_RDWR, os.ModeNamedPipe)

	if err != nil {
		if perr, ok := err.(*os.PathError); ok && perr.Err == syscall.ENOENT {
			err = attemptPiBlasterStart()
		}
	}

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

	return file
}

type Blaster struct {
	pipe  *os.File
	Input chan RGB
	Color chan func(RGB)
	shake chan struct{}

	r, g, b uint8
}

func NewBlaster() *Blaster {
	return &Blaster{
		Input: make(chan RGB),
		Color: make(chan func(RGB)),
		shake: make(chan struct{}),
	}
}

func (b *Blaster) Run() {
	b.pipe = mustOpenPiBlaster()
	defer b.pipe.Close()

	for {
		select {
		case c := <-b.Input:
			b.setAll(c)
		case c := <-b.Color:
			c(RGB{b.r, b.g, b.b})
		}
	}
}

func (b *Blaster) setPin(pin uint, val float64) error {
	chanCmd := fmt.Sprintf("%d=%f\n", pin, val)

	b.pipe.Write([]byte(chanCmd))
	return nil
}

func (b *Blaster) setChannelInteger(pin uint, val uint8) error {
	switch {
	case val > 255:
		return errors.New("can't go over 255. sorry mate.")
	case val < 0:
		return errors.New("can't go below 0. sorry mate.")
	default:
		fval := float64(val) / 255.0
		return b.setPin(pin, fval)
	}
}

func (b *Blaster) setRed(val uint8) error {
	return b.setChannelInteger(*flag_R, val)
}

func (b *Blaster) setGreen(val uint8) error {
	return b.setChannelInteger(*flag_G, val)
}

func (b *Blaster) setBlue(val uint8) error {
	return b.setChannelInteger(*flag_B, val)
}

type setterFunc func(v uint8) error

func (b *Blaster) setAll(c RGB) {
	b.setRed(c.R)
	b.setGreen(c.G)
	b.setBlue(c.B)
}

func errorHandler(w http.ResponseWriter, r *http.Request) {
	if err := recover(); err != nil {
		w.WriteHeader(401)

		fmt.Fprintf(w, "Oh...:(\n\n")

		if e, ok := err.(error); ok {
			w.Write([]byte(e.Error()))
			w.Write([]byte{'\n', '\n'})
			w.Write(debug.Stack())
		} else {
			fmt.Fprintf(w, "%s\n\n", err)
		}

		log.Println(
			"panic catched:", err,
			"\nRequest data:", r)
	}
}

func parseUint8OrZero(s string) uint8 {
	i, err := strconv.ParseUint(s, 10, 8)
	if err != nil {
		return 0
	}
	return uint8(i)
}

func actionHandler(w http.ResponseWriter, r *http.Request) {
	defer errorHandler(w, r)

	values, err := url.ParseQuery(r.URL.RawQuery)

	if err != nil {
		panic(err)
	}

	action := values.Get("action")

	switch action {
	case "lighter":
		blaster.Color <- func(c RGB) {
			blaster.Input <- RGB{c.R + 10, c.G + 10, c.B + 10}
		}
	case "darker":
		blaster.Color <- func(c RGB) {
			blaster.Input <- RGB{c.R - 10, c.G - 10, c.B - 10}
		}
	case "off":
		blaster.Input <- RGB{0, 0, 0}
	case "set":
		r := parseUint8OrZero(values.Get("r"))
		g := parseUint8OrZero(values.Get("g"))
		b := parseUint8OrZero(values.Get("b"))

		blaster.Input <- RGB{r, g, b}
	}
}

func currentColorHandler(w http.ResponseWriter, r *http.Request) {
	defer errorHandler(w, r)
	w.Header().Set("Content-Type", "text/plain")

	c := make(chan RGB)

	blaster.Color <- func(color RGB) {
		c <- color
	}

	w.Write([]byte((<-c).String()))
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
	defer errorHandler(w, r)
	templates.ExecuteTemplate(w, "index.html", nil)
}

func main() {
	flag.Parse()

	blaster = NewBlaster()
	go blaster.Run()

	blaster.Input <- RGB{}

	mustParseTemplates()

	http.HandleFunc("/", indexHandler)
	http.HandleFunc("/do", actionHandler)
	http.HandleFunc("/color", currentColorHandler)
	http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("templates"))))

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