<commit_before>package main

import (
	. "github.com/OUCC/syaro/logger"
	"github.com/OUCC/syaro/setting"
	"github.com/OUCC/syaro/wikiio"

	"flag"
	"html/template"
	"os"
	"path/filepath"
)

const (
	SYARO_REPOSITORY = "github.com/OUCC/syaro"
	PUBLIC_DIR       = "public"
	VIEWS_DIR        = "views"
)

var (
	views *template.Template
)

func main() {
	flag.Parse()
	SetupLogger()

	// print welcome message
	LoggerM.Println("===== Syaro Wiki Server =====")
	LoggerM.Println("Starting...")
	LoggerM.Println("")

	findSyaroDir()
	if setting.SyaroDir == "" {
		LoggerE.Fatalln("Error: Can't find system file directory.")
	}

	LoggerM.Println("WikiName:", setting.WikiName)
	LoggerM.Println("WikiRoot:", setting.WikiRoot)
	LoggerM.Println("Syaro dir:", setting.SyaroDir)
	if setting.FCGI {
		LoggerM.Println("Fast CGI mode: YES")
	} else {
		LoggerM.Println("Fast CGI mode: NO")
	}
	LoggerM.Println("Port:", setting.Port)
	LoggerM.Println("URL prefix:", setting.UrlPrefix)
	LoggerM.Println("")

	LoggerM.Println("Parsing template...")
	err := setupViews()
	if err != nil {
		LoggerE.Fatalln("Failed to parse template:", err)
	}
	LoggerM.Println("Template parsed")

	LoggerM.Println("Building index...")
	wikiio.BuildIndex()
	LoggerM.Println("Index built")

	startServer()
}

// findTemplateDir finds template directory contains html, css, etc...
// dir is directory specified by user as template dir.
// This search several directory and return right dir.
// If not found, return empty string.
func findSyaroDir() {
	// if syaro dir is specified by user, search this dir
	if setting.SyaroDir != "" {
		_, err := os.Stat(filepath.Join(setting.SyaroDir, VIEWS_DIR))
		// if directory isn't exist
		if err != nil {
			LoggerE.Println("Error: Can't find template file dir specified in argument")
			setting.SyaroDir = ""
			return
		}
	} else { // directory isn't specified by user so search it by myself
		// first, $GOROOT/src/...
		path := filepath.Join(os.Getenv("GOPATH"), "src", SYARO_REPOSITORY)
		_, err := os.Stat(filepath.Join(path, VIEWS_DIR))
		if err == nil {
			setting.SyaroDir = path
			return
		}

		// second, /usr/local/share/syaro
		path = "/usr/local/share/syaro"
		_, err = os.Stat(filepath.Join(path, VIEWS_DIR))
		if err == nil {
			setting.SyaroDir = path
			return
		}

		// third, C:\Program Files\Syaro (Windows)
		path = "/Program Files/Syaro"
		_, err = os.Stat(filepath.Join(path, VIEWS_DIR))
		if err == nil {
			setting.SyaroDir = path
			return
		}

		// can't find syaro dir
		setting.SyaroDir = ""
		return
	}
}

func setupViews() error {
	// funcs for template
	tmpl := template.New("").Funcs(template.FuncMap{
		"add":       func(a, b int) int { return a + b },
		"wikiName":  func() string { return setting.WikiName },
		"urlPrefix": func() string { return setting.UrlPrefix },
	})
	tmpl, err := tmpl.ParseGlob(filepath.Join(setting.SyaroDir, VIEWS_DIR, "*.html"))
	if err != nil {
		return err
	}

	views = tmpl
	return nil
}
<commit_msg>fix findSyaroDir()<commit_after>package main

import (
	. "github.com/OUCC/syaro/logger"
	"github.com/OUCC/syaro/setting"
	"github.com/OUCC/syaro/wikiio"

	"flag"
	"html/template"
	"os"
	"path/filepath"
)

const (
	SYARO_REPOSITORY = "github.com/OUCC/syaro"
	PUBLIC_DIR       = "public"
	VIEWS_DIR        = "views"
)

var (
	views *template.Template
)

func main() {
	flag.Parse()
	SetupLogger()

	// print welcome message
	LoggerM.Println("===== Syaro Wiki Server =====")
	LoggerM.Println("Starting...")
	LoggerM.Println("")

	findSyaroDir()
	if setting.SyaroDir == "" {
		LoggerE.Fatalln("Error: Can't find system file directory.")
	}

	LoggerM.Println("WikiName:", setting.WikiName)
	LoggerM.Println("WikiRoot:", setting.WikiRoot)
	LoggerM.Println("Syaro dir:", setting.SyaroDir)
	if setting.FCGI {
		LoggerM.Println("Fast CGI mode: YES")
	} else {
		LoggerM.Println("Fast CGI mode: NO")
	}
	LoggerM.Println("Port:", setting.Port)
	LoggerM.Println("URL prefix:", setting.UrlPrefix)
	LoggerM.Println("")

	LoggerM.Println("Parsing template...")
	err := setupViews()
	if err != nil {
		LoggerE.Fatalln("Failed to parse template:", err)
	}
	LoggerM.Println("Template parsed")

	LoggerM.Println("Building index...")
	wikiio.BuildIndex()
	LoggerM.Println("Index built")

	startServer()
}

// findTemplateDir finds template directory contains html, css, etc...
// dir is directory specified by user as template dir.
// This search several directory and return right dir.
// If not found, return empty string.
func findSyaroDir() {
	// if syaro dir is specified by user, search this dir
	if setting.SyaroDir != "" {
		_, err := os.Stat(filepath.Join(setting.SyaroDir, VIEWS_DIR))
		// if directory isn't exist
		if err != nil {
			LoggerE.Println("Error: Can't find template file dir specified in argument")
			setting.SyaroDir = ""
			return
		}
	} else { // directory isn't specified by user so search it by myself
		paths := []string{
			".",
			"/usr/local/share/syaro",
			"/Program Files/Syaro",
		}

		for _, path := range paths {
			_, err := os.Stat(filepath.Join(path, VIEWS_DIR))
			if err == nil {
				setting.SyaroDir = path
				return
			}
		}

		// can't find syaro dir
		setting.SyaroDir = ""
		return
	}
}

func setupViews() error {
	// funcs for template
	tmpl := template.New("").Funcs(template.FuncMap{
		"add":       func(a, b int) int { return a + b },
		"wikiName":  func() string { return setting.WikiName },
		"urlPrefix": func() string { return setting.UrlPrefix },
	})
	tmpl, err := tmpl.ParseGlob(filepath.Join(setting.SyaroDir, VIEWS_DIR, "*.html"))
	if err != nil {
		return err
	}

	views = tmpl
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"strings"
)

func check(err error) {
	if err != nil {
		panic(err)
	}
}

func main() {

	github_user := os.Getenv("GITHUB_USER")
	github_password := os.Getenv("GITHUB_PASSWORD")

	json := `{ "name": "web",
                   "config": {"url": "http://services.scraperwiki.com/hook",
                              "content_type": "json"},
	          "events": ["push", "issues", "issue_comment",
                             "commit_comment", "create", "delete",
                             "pull_request", "pull_request_review_comment",
                             "gollum", "watch", "release", "fork", "member",
                             "public", "team_add", "status"],
                  "active": true
                 }`

	Endpoint := "https://" + github_user + ":" + github_password + "@" + "api.github.com"

	buffer := strings.NewReader(json)
	resp, err := http.Post(Endpoint+"/repos/scraperwiki/custard/hooks", "application/json", buffer)
	check(err)

	response, err := ioutil.ReadAll(resp.Body)
	check(err)

	log.Print(string(response))

	http.HandleFunc("/hook", handleRoot)
	log.Fatal(http.ListenAndServe(":80", nil))

}

func handleRoot(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello, world!\n")
	request, err := ioutil.ReadAll(r.Body)
	check(err)
	var dst bytes.Buffer
	r.Header.Write(&dst)
	log.Println("Incoming request headers: ", string(dst.Bytes()))

	dst.Reset()
	err = json.Indent(&dst, request, "", "  ")
	check(err)
	log.Println("Incoming request: ", string(dst.Bytes()))
}
<commit_msg>Switch on X-Github-Event header.<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"strings"
)

func check(err error) {
	if err != nil {
		panic(err)
	}
}

func main() {

	github_user := os.Getenv("GITHUB_USER")
	github_password := os.Getenv("GITHUB_PASSWORD")

	json := `{ "name": "web",
                   "config": {"url": "http://services.scraperwiki.com/hook",
                              "content_type": "json"},
	          "events": ["push", "issues", "issue_comment",
                             "commit_comment", "create", "delete",
                             "pull_request", "pull_request_review_comment",
                             "gollum", "watch", "release", "fork", "member",
                             "public", "team_add", "status"],
                  "active": true
                 }`

	Endpoint := "https://" + github_user + ":" + github_password + "@" + "api.github.com"

	buffer := strings.NewReader(json)
	resp, err := http.Post(Endpoint+"/repos/scraperwiki/custard/hooks", "application/json", buffer)
	check(err)

	response, err := ioutil.ReadAll(resp.Body)
	check(err)

	log.Print(string(response))

	http.HandleFunc("/hook", handleRoot)
	log.Fatal(http.ListenAndServe(":80", nil))

}

func handleRoot(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello, world!\n")
	request, err := ioutil.ReadAll(r.Body)
	check(err)
	var dst bytes.Buffer
	r.Header.Write(&dst)
	log.Println("Incoming request headers: ", string(dst.Bytes()))
	dst.Reset()
	err = json.Indent(&dst, request, "", "  ")
	check(err)

	log.Println("Incoming request:", string(dst.Bytes()))

	switch eventType := r.Header["X-Github-Event"][0]; eventType {
	case "push":
		log.Println("Pushed")
	default:
		log.Println("Unhandled event:", eventType)
	}

}
<|endoftext|>


<commit_before>// Rescue as much as possible from your Picturelife account
package main

import (
	"bufio"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"net/url"
	"os"
	"regexp"
	"strconv"
	"strings"
	"sync"

	"golang.org/x/crypto/ssh/terminal"

	"github.com/cheggaaa/pb"
)

type Media struct {
	Id             string `json:"id"`
	MediaType      string `json:"media_type"`
	Format         string `json:"format"`
	Processed      bool   `json:"processed"`
	CreatedAt      int    `json:"created_at"`
	UpdatedAt      int    `json:"updated_at"`
	TakenAt        int    `json:"taken_at"`
	Width          int    `json:"width"`
	Height         int    `json:"height"`
	Orientation    int    `json:"orientation"`
	Privacy        int    `json:"privacy"`
	IsBestPhoto    bool   `json:"is_best_photo"`
	TimeZoneOffset int    `json:"time_zone_offset"`
	Hidden         bool   `json:"hidden"`
	Visible        bool   `json:"visible"`
	Filesize       int    `json:"filesize"`
	BucketId       int    `json:"bucket_id"`
	Status         string `json:"status"`
	Retries        int    `json:"retries"`
}

type APIResponse struct {
	Status       int     `json:"status"`
	Media        []Media `json:"media"`
	Total        int     `json:"total"`
	Limit        int     `json:"limit"`
	Offset       int     `json:"offset"`
	UsingCache   bool    `json:"using_cache"`
	ResponseTime int     `json:"response_time"`
}

var (
	loginUrl    *url.URL
	signinUrl   *url.URL
	apiPageUrl  *url.URL
	apiUrl      *url.URL
	originalUrl *url.URL

	signinValues url.Values

	accessTokenRE *regexp.Regexp
	accessToken   string

	pathPerm os.FileMode = 0770
	filePerm os.FileMode = 0770

	mediaPath string = "picturelife"
	indexPath string = "pl_index.json"

	// Flags
	retryFlag  bool = false // Retry failed images and videos?
	helpFlag   bool = false // Retry failed images and videos?
	statusFlag bool = false // Retry failed images and videos?
)

func init() {
	var err error

	flag.BoolVar(&retryFlag, "retry", retryFlag, "Retry failed images and videos?")
	flag.BoolVar(&helpFlag, "help", helpFlag, "Print help text")
	flag.BoolVar(&statusFlag, "status", statusFlag, "Print out current status")

	loginUrl, err = url.Parse("http://picturelife.com/login")
	if err != nil {
		panic("Unable to parse login URL")
	}

	// Login posts to this
	signinUrl, err = url.Parse("http://picturelife.com/signin")
	if err != nil {
		panic("Unable to parse sign in URL")
	}

	apiPageUrl, err = url.Parse("http://picturelife.com/api")
	if err != nil {
		panic("Unable to parse API Page URL")
	}

	originalUrl, err = url.Parse("http://picturelife.com/d/original/")
	if err != nil {
		panic("Unable to parse API Page URL")
	}

	accessTokenRE = regexp.MustCompile("<script>\\s*pl\\.access_token\\s*=\\s*'([^']+)';\\s*pl\\.api_url\\s*=\\s*'([^']+)'\\s*</script>")

	err = os.MkdirAll(mediaPath, pathPerm)
	if err != nil {
		panic(err)
	}
}

func main() {
	flag.Parse()

	if helpFlag {
		printHelp()
		return
	}

	if statusFlag {
		printStatus()
		return
	}

	// Instantiate the crawler
	client := NewCrawler()

	// Ask for email and password
	signinValues := getCredentials()

	res := client.GetOrDie(loginUrl.String())
	res.Body.Close()

	res = client.PostFormOrDie(signinUrl.String(), signinValues)
	body, err := ioutil.ReadAll(res.Body)
	res.Body.Close()

	if strings.Contains(string(body), "Login error! Please check your email and password.") {
		fmt.Println("Login error! Please check your email and password.")
		return
	}

	res = client.GetOrDie(apiPageUrl.String())
	body, err = ioutil.ReadAll(res.Body)
	res.Body.Close()

	fmt.Print("Trying to extract Access Token and API URL...")
	parts := accessTokenRE.FindStringSubmatch(string(body))
	if len(parts) != 3 {
		fmt.Println("\nUnable to extract Access Token and API URL.")
		fmt.Println("This is the source code received:")
		fmt.Println(string(body))
		return
	}
	fmt.Println(" Done!")

	accessToken = parts[1]
	apiUrl, err = url.Parse(parts[2])
	if err != nil {
		fmt.Println("Unable to parse API Page URL")
		return
	}

	// So far, so good... Now extract the index json, if it hasn't already been done

	// If the JSON index file does not exist, we'll fetch it from the API and create it

	if _, err := os.Stat(indexPath); os.IsNotExist(err) {
		fmt.Println("\nTrying to extract index of all files...")

		var progress *pb.ProgressBar
		var allMedia []Media

		indexUrl := apiUrl.String() + "/media/index"

		offset := 0
		limit := 500
		total := -1

		formValues := url.Values{
			"taken_at_after":      {"0"},
			"include_hidden":      {"true"},
			"show_invisible":      {"true"},
			"warm_thumbs":         {"false"},
			"include_names":       {"false"},
			"include_comments":    {"false"},
			"include_signature":   {"false"},
			"include_access_info": {"false"},
			"include_likes":       {"false"},
			"offset":              {strconv.Itoa(offset)},
			"limit":               {strconv.Itoa(limit)},
			"access_token":        {accessToken},
		}

		for total == -1 || offset < total {
			formValues.Set("offset", strconv.Itoa(offset))

			res := client.PostFormOrDie(indexUrl, formValues)
			body, err = ioutil.ReadAll(res.Body)
			res.Body.Close()

			var apiResponse APIResponse
			err := json.Unmarshal(body, &apiResponse)
			if err != nil {
				fmt.Println("ERROR! Unable to read JSON response from API. Please try again later.")
				os.Exit(0)
			}

			allMedia = append(allMedia, apiResponse.Media...)
			total = apiResponse.Total

			if progress == nil {
				progress = pb.New(total)
				progress.ShowCounters = true
				progress.ShowTimeLeft = true
				progress.Start()
			}

			progress.Set(offset)

			offset += limit
		}

		progress.FinishPrint("Done fetching JSON index")

		mediaJson, _ := json.Marshal(allMedia)
		err = ioutil.WriteFile(indexPath, mediaJson, filePerm)

		if err != nil {
			fmt.Println("ERROR! Unable to write JSON index file to disk. Sorry...")
			fmt.Println("Please go to GitHub and open an issue.")
			os.Exit(0)
		}
	}

	if _, err := os.Stat(indexPath); os.IsNotExist(err) {
		fmt.Println("ERROR! Unable to find the JSON index file from disk. Sorry...")
		fmt.Println("Please go to GitHub and open an issue.")
		os.Exit(0)
	}

	src, err := ioutil.ReadFile(indexPath)
	if err != nil {
		fmt.Println("ERROR! Unable to read the JSON index file from disk. Sorry...")
		fmt.Println("Please go to GitHub and open an issue.")
		os.Exit(0)
	}

	var allMedia []Media

	json.Unmarshal(src, &allMedia)

	fmt.Println("\nTrying to extract pictures and videos...")

	ch := make(chan bool, 10)
	mediaLock := sync.Mutex{}

	progressCount := len(allMedia)
	for i, media := range allMedia {
		filePath := mediaPath + "/" + getMediaFilename(&media)

		if _, err := os.Stat(filePath); os.IsNotExist(err) {
			allMedia[i].Status = ""
		} else if media.Status == "done" {
			progressCount--
		} else if !retryFlag && media.Status == "failed" {
			progressCount--
		}
	}

	progress := pb.New(progressCount)
	progress.ShowCounters = true
	progress.ShowTimeLeft = true
	progress.Start()

	fails := 0
	success := 0
	for i, media := range allMedia {
		if allMedia[i].Status == "done" {
			success += 1
			continue
		}

		if !retryFlag && allMedia[i].Status == "failed" {
			fails += 1
			continue
		}

		ch <- true

		go func(index int, media *Media) {
			fetchMedia(&client, media)
			mediaLock.Lock()
			allMedia[index] = *media
			if media.Status == "done" {
				success += 1
			} else {
				fails += 1
			}
			progress.Increment()
			mediaLock.Unlock()
			<-ch
		}(i, &media)

		if i > 0 && i%10 == 0 {
			mediaJson, _ := json.Marshal(allMedia)
			err = ioutil.WriteFile(indexPath, mediaJson, filePerm)

			if err != nil {
				fmt.Println("ERROR! Unable to write update JSON index file to disk. Sorry...")
				fmt.Println("Please go to GitHub and open an issue.")
				os.Exit(0)
			}
		}
	}

	mediaJson, _ := json.Marshal(allMedia)
	err = ioutil.WriteFile(indexPath, mediaJson, filePerm)

	if err != nil {
		fmt.Println("ERROR! Unable to write update JSON index file to disk. Sorry...")
		fmt.Println("Please go to GitHub and open an issue.")
		os.Exit(0)
	}

	progress.Finish()

	fmt.Println("Done trying to fetch all pictures and videos.")
	fmt.Println("Result:")
	fmt.Println("\tSuccess:", success)
	fmt.Println("\tFailed: ", fails)
}

func fetchMedia(client *Crawler, media *Media) {
	media.Retries += 1
	media.Status = "started"

	filename := getMediaFilename(media)

	filePath := mediaPath + "/" + filename
	url := originalUrl.String() + media.Id

	out, err := os.Create(filePath)
	if err != nil {
		media.Status = "failed"
		out.Close()
		os.Remove(filePath)
		return
	}

	res, err := client.Client.Get(url)
	if err != nil || res.StatusCode != 200 {
		media.Status = "failed"
		out.Close()
		if res != nil {
			res.Body.Close()
		}
		os.Remove(filePath)
		return
	}

	n, err := io.Copy(out, res.Body)
	if err != nil {
		media.Status = "failed"
		out.Close()
		res.Body.Close()
		os.Remove(filePath)
		return
	}

	if n < 1000 {
		media.Status = "failed"
		out.Close()
		res.Body.Close()
		os.Remove(filePath)

	} else {
		media.Status = "done"
		out.Close()
		res.Body.Close()
	}
}

func getMediaFilename(media *Media) (filename string) {
	extension := strings.ToLower(media.Format)
	extension = strings.Replace(extension, "jpeg", "jpg", 1)
	filename = media.Id + "." + extension
	return
}

func printHelp() {
	fmt.Println("Currently you can only choose whether or not to retry failed fetches")
	flag.PrintDefaults()
	fmt.Println("")
	fmt.Println("Usage:")
	fmt.Println(`./rescuelife -retry`)
	fmt.Println("")
}

func printStatus() {
	if _, err := os.Stat(indexPath); os.IsNotExist(err) {
		fmt.Println("ERROR! Unable to find the JSON index file from disk. Sorry...")
		return
	}

	src, err := ioutil.ReadFile(indexPath)
	if err != nil {
		fmt.Println("ERROR! Unable to read the JSON index file from disk. Sorry...")
		return
	}

	var allMedia []Media

	json.Unmarshal(src, &allMedia)

	var failed, started, done, waiting int
	total := len(allMedia)
	for _, media := range allMedia {
		switch media.Status {
		case "done":
			done++
		case "started":
			started++
		case "failed":
			failed++
		default:
			waiting++
		}
	}

	fmt.Println("\nStatus for fetching")
	fmt.Println("-----------------------------")
	fmt.Println("Succeeded:", done)
	fmt.Println("Failed:   ", failed)
	fmt.Println("Fetching: ", started)
	fmt.Println("Waiting:  ", waiting)
	fmt.Println("Total:    ", total)
	fmt.Println("")
}

func getCredentials() (signinValues url.Values) {
	fmt.Println("\n---------------------------------------------------------------------------------------------------------------------")
	fmt.Println("Your email and password is needed in order to get a cookie, extract Access Token and to fetch your images and videos.")
	fmt.Println("Nothing will be stored or copied to any other server.")
	fmt.Println("---------------------------------------------------------------------------------------------------------------------\n")

	reader := bufio.NewReader(os.Stdin)

	fmt.Print("Your email: ")
	email, _ := reader.ReadString('\n')
	email = strings.Trim(email, "\n")

	fmt.Print("Your password: ")
	bytePassword, _ := terminal.ReadPassword(0)
	password := strings.Trim(string(bytePassword), "\n")
	fmt.Println("\n")

	if email == "" || password == "" {
		fmt.Println("ERROR! Please provide email and password")
		os.Exit(0)
	}

	signinValues = url.Values{"email": {email}, "password": {password}}

	return
}
<commit_msg>More status fixing<commit_after>// Rescue as much as possible from your Picturelife account
package main

import (
	"bufio"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"net/url"
	"os"
	"regexp"
	"strconv"
	"strings"
	"sync"

	"golang.org/x/crypto/ssh/terminal"

	"github.com/cheggaaa/pb"
)

type Media struct {
	Id             string `json:"id"`
	MediaType      string `json:"media_type"`
	Format         string `json:"format"`
	Processed      bool   `json:"processed"`
	CreatedAt      int    `json:"created_at"`
	UpdatedAt      int    `json:"updated_at"`
	TakenAt        int    `json:"taken_at"`
	Width          int    `json:"width"`
	Height         int    `json:"height"`
	Orientation    int    `json:"orientation"`
	Privacy        int    `json:"privacy"`
	IsBestPhoto    bool   `json:"is_best_photo"`
	TimeZoneOffset int    `json:"time_zone_offset"`
	Hidden         bool   `json:"hidden"`
	Visible        bool   `json:"visible"`
	Filesize       int    `json:"filesize"`
	BucketId       int    `json:"bucket_id"`
	Status         string `json:"status"`
	Retries        int    `json:"retries"`
}

type APIResponse struct {
	Status       int     `json:"status"`
	Media        []Media `json:"media"`
	Total        int     `json:"total"`
	Limit        int     `json:"limit"`
	Offset       int     `json:"offset"`
	UsingCache   bool    `json:"using_cache"`
	ResponseTime int     `json:"response_time"`
}

var (
	loginUrl    *url.URL
	signinUrl   *url.URL
	apiPageUrl  *url.URL
	apiUrl      *url.URL
	originalUrl *url.URL

	signinValues url.Values

	accessTokenRE *regexp.Regexp
	accessToken   string

	pathPerm os.FileMode = 0770
	filePerm os.FileMode = 0770

	mediaPath string = "picturelife"
	indexPath string = "pl_index.json"

	// Flags
	retryFlag  bool = false // Retry failed images and videos?
	helpFlag   bool = false // Retry failed images and videos?
	statusFlag bool = false // Retry failed images and videos?
)

func init() {
	var err error

	flag.BoolVar(&retryFlag, "retry", retryFlag, "Retry failed images and videos?")
	flag.BoolVar(&helpFlag, "help", helpFlag, "Print help text")
	flag.BoolVar(&statusFlag, "status", statusFlag, "Print out current status")

	loginUrl, err = url.Parse("http://picturelife.com/login")
	if err != nil {
		panic("Unable to parse login URL")
	}

	// Login posts to this
	signinUrl, err = url.Parse("http://picturelife.com/signin")
	if err != nil {
		panic("Unable to parse sign in URL")
	}

	apiPageUrl, err = url.Parse("http://picturelife.com/api")
	if err != nil {
		panic("Unable to parse API Page URL")
	}

	originalUrl, err = url.Parse("http://picturelife.com/d/original/")
	if err != nil {
		panic("Unable to parse API Page URL")
	}

	accessTokenRE = regexp.MustCompile("<script>\\s*pl\\.access_token\\s*=\\s*'([^']+)';\\s*pl\\.api_url\\s*=\\s*'([^']+)'\\s*</script>")

	err = os.MkdirAll(mediaPath, pathPerm)
	if err != nil {
		panic(err)
	}
}

func main() {
	flag.Parse()

	if helpFlag {
		printHelp()
		return
	}

	if statusFlag {
		printStatus()
		return
	}

	// Instantiate the crawler
	client := NewCrawler()

	// Ask for email and password
	signinValues := getCredentials()

	res := client.GetOrDie(loginUrl.String())
	res.Body.Close()

	res = client.PostFormOrDie(signinUrl.String(), signinValues)
	body, err := ioutil.ReadAll(res.Body)
	res.Body.Close()

	if strings.Contains(string(body), "Login error! Please check your email and password.") {
		fmt.Println("Login error! Please check your email and password.")
		return
	}

	res = client.GetOrDie(apiPageUrl.String())
	body, err = ioutil.ReadAll(res.Body)
	res.Body.Close()

	fmt.Print("Trying to extract Access Token and API URL...")
	parts := accessTokenRE.FindStringSubmatch(string(body))
	if len(parts) != 3 {
		fmt.Println("\nUnable to extract Access Token and API URL.")
		fmt.Println("This is the source code received:")
		fmt.Println(string(body))
		return
	}
	fmt.Println(" Done!")

	accessToken = parts[1]
	apiUrl, err = url.Parse(parts[2])
	if err != nil {
		fmt.Println("Unable to parse API Page URL")
		return
	}

	// So far, so good... Now extract the index json, if it hasn't already been done

	// If the JSON index file does not exist, we'll fetch it from the API and create it

	if _, err := os.Stat(indexPath); os.IsNotExist(err) {
		fmt.Println("\nTrying to extract index of all files...")

		var progress *pb.ProgressBar
		var allMedia []Media

		indexUrl := apiUrl.String() + "/media/index"

		offset := 0
		limit := 500
		total := -1

		formValues := url.Values{
			"taken_at_after":      {"0"},
			"include_hidden":      {"true"},
			"show_invisible":      {"true"},
			"warm_thumbs":         {"false"},
			"include_names":       {"false"},
			"include_comments":    {"false"},
			"include_signature":   {"false"},
			"include_access_info": {"false"},
			"include_likes":       {"false"},
			"offset":              {strconv.Itoa(offset)},
			"limit":               {strconv.Itoa(limit)},
			"access_token":        {accessToken},
		}

		for total == -1 || offset < total {
			formValues.Set("offset", strconv.Itoa(offset))

			res := client.PostFormOrDie(indexUrl, formValues)
			body, err = ioutil.ReadAll(res.Body)
			res.Body.Close()

			var apiResponse APIResponse
			err := json.Unmarshal(body, &apiResponse)
			if err != nil {
				fmt.Println("ERROR! Unable to read JSON response from API. Please try again later.")
				os.Exit(0)
			}

			allMedia = append(allMedia, apiResponse.Media...)
			total = apiResponse.Total

			if progress == nil {
				progress = pb.New(total)
				progress.ShowCounters = true
				progress.ShowTimeLeft = true
				progress.Start()
			}

			progress.Set(offset)

			offset += limit
		}

		progress.FinishPrint("Done fetching JSON index")

		mediaJson, _ := json.Marshal(allMedia)
		err = ioutil.WriteFile(indexPath, mediaJson, filePerm)

		if err != nil {
			fmt.Println("ERROR! Unable to write JSON index file to disk. Sorry...")
			fmt.Println("Please go to GitHub and open an issue.")
			os.Exit(0)
		}
	}

	if _, err := os.Stat(indexPath); os.IsNotExist(err) {
		fmt.Println("ERROR! Unable to find the JSON index file from disk. Sorry...")
		fmt.Println("Please go to GitHub and open an issue.")
		os.Exit(0)
	}

	src, err := ioutil.ReadFile(indexPath)
	if err != nil {
		fmt.Println("ERROR! Unable to read the JSON index file from disk. Sorry...")
		fmt.Println("Please go to GitHub and open an issue.")
		os.Exit(0)
	}

	var allMedia []Media

	json.Unmarshal(src, &allMedia)

	fmt.Println("\nTrying to extract pictures and videos...")

	ch := make(chan bool, 10)
	mediaLock := sync.Mutex{}

	progressCount := len(allMedia)
	for i, media := range allMedia {
		filePath := mediaPath + "/" + getMediaFilename(&media)

		if _, err := os.Stat(filePath); os.IsNotExist(err) {
			allMedia[i].Status = ""
		} else if media.Status == "started" {
			allMedia[i].Status = ""
		} else if media.Status == "done" {
			progressCount--
		} else if !retryFlag && media.Status == "failed" {
			progressCount--
		}
	}

	progress := pb.New(progressCount)
	progress.ShowCounters = true
	progress.ShowTimeLeft = true
	progress.Start()

	fails := 0
	success := 0
	for i, media := range allMedia {
		if allMedia[i].Status == "done" {
			success += 1
			continue
		}

		if !retryFlag && allMedia[i].Status == "failed" {
			fails += 1
			continue
		}

		ch <- true

		go func(index int, media *Media) {
			fetchMedia(&client, media)
			mediaLock.Lock()
			allMedia[index] = *media
			if media.Status == "done" {
				success += 1
			} else {
				fails += 1
			}
			progress.Increment()
			mediaLock.Unlock()
			<-ch
		}(i, &media)

		if i > 0 && i%10 == 0 {
			mediaJson, _ := json.Marshal(allMedia)
			err = ioutil.WriteFile(indexPath, mediaJson, filePerm)

			if err != nil {
				fmt.Println("ERROR! Unable to write update JSON index file to disk. Sorry...")
				fmt.Println("Please go to GitHub and open an issue.")
				os.Exit(0)
			}
		}
	}

	mediaJson, _ := json.Marshal(allMedia)
	err = ioutil.WriteFile(indexPath, mediaJson, filePerm)

	if err != nil {
		fmt.Println("ERROR! Unable to write update JSON index file to disk. Sorry...")
		fmt.Println("Please go to GitHub and open an issue.")
		os.Exit(0)
	}

	progress.Finish()

	fmt.Println("Done trying to fetch all pictures and videos.")
	fmt.Println("Result:")
	fmt.Println("\tSuccess:", success)
	fmt.Println("\tFailed: ", fails)
}

func fetchMedia(client *Crawler, media *Media) {
	media.Retries += 1
	media.Status = "started"

	filename := getMediaFilename(media)

	filePath := mediaPath + "/" + filename
	url := originalUrl.String() + media.Id

	out, err := os.Create(filePath)
	if err != nil {
		media.Status = "failed"
		out.Close()
		os.Remove(filePath)
		return
	}

	res, err := client.Client.Get(url)
	if err != nil || res.StatusCode != 200 {
		media.Status = "failed"
		out.Close()
		if res != nil {
			res.Body.Close()
		}
		os.Remove(filePath)
		return
	}

	n, err := io.Copy(out, res.Body)
	if err != nil {
		media.Status = "failed"
		out.Close()
		res.Body.Close()
		os.Remove(filePath)
		return
	}

	if n < 1000 {
		media.Status = "failed"
		out.Close()
		res.Body.Close()
		os.Remove(filePath)

	} else {
		media.Status = "done"
		out.Close()
		res.Body.Close()
	}
}

func getMediaFilename(media *Media) (filename string) {
	extension := strings.ToLower(media.Format)
	extension = strings.Replace(extension, "jpeg", "jpg", 1)
	filename = media.Id + "." + extension
	return
}

func printHelp() {
	fmt.Println("Currently you can only choose whether or not to retry failed fetches")
	flag.PrintDefaults()
	fmt.Println("")
	fmt.Println("Usage:")
	fmt.Println(`./rescuelife -retry`)
	fmt.Println("")
}

func printStatus() {
	if _, err := os.Stat(indexPath); os.IsNotExist(err) {
		fmt.Println("ERROR! Unable to find the JSON index file from disk. Sorry...")
		return
	}

	src, err := ioutil.ReadFile(indexPath)
	if err != nil {
		fmt.Println("ERROR! Unable to read the JSON index file from disk. Sorry...")
		return
	}

	var allMedia []Media

	json.Unmarshal(src, &allMedia)

	var failed, started, done, waiting int
	total := len(allMedia)
	for _, media := range allMedia {
		switch media.Status {
		case "done":
			done++
		case "started":
			started++
		case "failed":
			failed++
		default:
			waiting++
		}
	}

	fmt.Println("\nStatus for fetching")
	fmt.Println("-----------------------------")
	fmt.Println("Succeeded:", done)
	fmt.Println("Failed:   ", failed)
	fmt.Println("Fetching: ", started)
	fmt.Println("Waiting:  ", waiting)
	fmt.Println("Total:    ", total)
	fmt.Println("")
}

func getCredentials() (signinValues url.Values) {
	fmt.Println("\n---------------------------------------------------------------------------------------------------------------------")
	fmt.Println("Your email and password is needed in order to get a cookie, extract Access Token and to fetch your images and videos.")
	fmt.Println("Nothing will be stored or copied to any other server.")
	fmt.Println("---------------------------------------------------------------------------------------------------------------------\n")

	reader := bufio.NewReader(os.Stdin)

	fmt.Print("Your email: ")
	email, _ := reader.ReadString('\n')
	email = strings.Trim(email, "\n")

	fmt.Print("Your password: ")
	bytePassword, _ := terminal.ReadPassword(0)
	password := strings.Trim(string(bytePassword), "\n")
	fmt.Println("\n")

	if email == "" || password == "" {
		fmt.Println("ERROR! Please provide email and password")
		os.Exit(0)
	}

	signinValues = url.Values{"email": {email}, "password": {password}}

	return
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"time"

	"github.com/hybridgroup/gobot"
	"github.com/hybridgroup/gobot/platforms/gpio"
	"github.com/hybridgroup/gobot/platforms/raspi"
)

const (
	MODE_DEBUG string = "debug"
	MODE_PI    string = "pi"
)

var Mode string = MODE_PI
var GameEvents chan string

func main() {

	// initialize our base configuration for the system
	initConfiguration()

	// initialize a new gobot
	gbot := gobot.NewGobot()

	// initialize a raspberry pi adaptor
	raspberry := raspi.NewRaspiAdaptor("raspi")

	// start/stop button for a woman
	buttonWoman := gpio.NewButtonDriver(raspberry, "buttonWoman", "11")
	ledWoman := gpio.NewLedDriver(raspberry, "ledWoman", "25") // or 12

	// start/stop buttom for a man
	// buttonMan := gpio.NewButtonDriver(raspberry, "buttonMan", "32")
	// ledMan := gpio.NewLedDriver(raspberry, "ledMan", "18")

	// contact with the wire (start- and finish-area)
	// contactStart := gpio.NewButtonDriver(raspberry, "contactStart", "33")
	contactFinish := gpio.NewButtonDriver(raspberry, "contactFinish", "35")

	// user made contact with the wire (use buzzer to indicate audible)
	contactWire := gpio.NewButtonDriver(raspberry, "contactWire", "15")
	buzzer := gpio.NewLedDriver(raspberry, "buzzer", "16")

	// create a channel for game events
	GameEvents = make(chan string)

	// simulate events with keyboard interaction
	go simulate(GameEvents)

	// define the work to be done by the robot (i.e. react to events)
	work := func() {

		// user pushed the start/stop button
		gobot.On(buttonWoman.Event("push"), func(data interface{}) {
			handleButtonPress(FEMALE)
		})

		// gobot.On(buttonMan.Event("push"), func(data interface{}) {
		// 	handleButtonPress(MALE)
		// })

		// user made contact with wire
		gobot.On(contactWire.Event("push"), handleWireContact)

		// user is starting the game (must touch the starting area)
		// TODO: add handler for starting event
		// gobot.On(contactStart.Event("push"), handleStartContact)

		// user finished the game (touched finish area)
		gobot.On(contactFinish.Event("push"), handleFinishContact)

		go func() {

			for event := range GameEvents {

				switch event {
				// sound the buzzer
				case "soundBuzzer":
					go func() {
						buzzer.On()
						<-time.After(300 * time.Millisecond)
						buzzer.Off()
					}()

				// enable/disable the led for the woman button
				case "enableLedWoman":
					ledWoman.On()
				case "disableLedWoman":
					ledWoman.Off()

				// enable/disable the lef for the man button
				// case "enableLedMan":
				// 	ledMan.On()
				// case "disableLedMan":
				// 	ledMan.Off()

				// disable all leds
				case "ledOff":
					ledWoman.Off()
					// ledMan.Off()

				// simulated events
				case "button":
					handleButtonPress(FEMALE)
				case "contact":
					handleWireContact(nil)
				case "start":
					handleStartContact(nil)
				case "finish":
					handleFinishContact(nil)
				}
			}
		}()

	}

	// we need to define a robot to be used with gobot
	var robot *gobot.Robot

	// switch cases depending on the mode
	if Mode == MODE_DEBUG {
		// debug mode is run on the mac without physical connections
		fmt.Println("RUNNING IN DEBUG-MODE")

		robot = gobot.NewRobot("buzzwire",
			[]gobot.Connection{},
			[]gobot.Device{},
			work)
	} else {
		// all other modes are run on the pi with physical connections
		robot = gobot.NewRobot("buzzwire",
			[]gobot.Connection{raspberry},
			[]gobot.Device{buttonWoman, ledWoman, contactWire, buzzer, contactFinish},
			work)
	}

	// add the robot to the fleet
	gbot.AddRobot(robot)

	// start the webserver in a separate go routine
	go startServer("localhost:8484")

	// start the robot (blocking)
	gbot.Start()
}
<commit_msg>change gpio pins<commit_after>package main

import (
	"fmt"
	"time"

	"github.com/hybridgroup/gobot"
	"github.com/hybridgroup/gobot/platforms/gpio"
	"github.com/hybridgroup/gobot/platforms/raspi"
)

const (
	MODE_DEBUG string = "debug"
	MODE_PI    string = "pi"
)

var Mode string = MODE_PI
var GameEvents chan string

func main() {

	// initialize our base configuration for the system
	initConfiguration()

	// initialize a new gobot
	gbot := gobot.NewGobot()

	// initialize a raspberry pi adaptor
	raspberry := raspi.NewRaspiAdaptor("raspi")

	// start/stop button for a woman
	buttonWoman := gpio.NewButtonDriver(raspberry, "buttonWoman", "11")
	ledWoman := gpio.NewLedDriver(raspberry, "ledWoman", "12")

	// start/stop buttom for a man
	// buttonMan := gpio.NewButtonDriver(raspberry, "buttonMan", "32")
	// ledMan := gpio.NewLedDriver(raspberry, "ledMan", "18")

	// contact with the wire (start- and finish-area)
	// contactStart := gpio.NewButtonDriver(raspberry, "contactStart", "33")
	contactFinish := gpio.NewButtonDriver(raspberry, "contactFinish", "36")

	// user made contact with the wire (use buzzer to indicate audible)
	contactWire := gpio.NewButtonDriver(raspberry, "contactWire", "15")
	buzzer := gpio.NewLedDriver(raspberry, "buzzer", "16")

	// create a channel for game events
	GameEvents = make(chan string)

	// simulate events with keyboard interaction
	go simulate(GameEvents)

	// define the work to be done by the robot (i.e. react to events)
	work := func() {

		// user pushed the start/stop button
		gobot.On(buttonWoman.Event("push"), func(data interface{}) {
			handleButtonPress(FEMALE)
		})

		// gobot.On(buttonMan.Event("push"), func(data interface{}) {
		// 	handleButtonPress(MALE)
		// })

		// user made contact with wire
		gobot.On(contactWire.Event("push"), handleWireContact)

		// user is starting the game (must touch the starting area)
		// TODO: add handler for starting event
		// gobot.On(contactStart.Event("push"), handleStartContact)

		// user finished the game (touched finish area)
		gobot.On(contactFinish.Event("push"), handleFinishContact)

		go func() {

			for event := range GameEvents {

				switch event {
				// sound the buzzer
				case "soundBuzzer":
					go func() {
						buzzer.On()
						<-time.After(300 * time.Millisecond)
						buzzer.Off()
					}()

				// enable/disable the led for the woman button
				case "enableLedWoman":
					ledWoman.On()
				case "disableLedWoman":
					ledWoman.Off()

				// enable/disable the lef for the man button
				// case "enableLedMan":
				// 	ledMan.On()
				// case "disableLedMan":
				// 	ledMan.Off()

				// disable all leds
				case "ledOff":
					ledWoman.Off()
					// ledMan.Off()

				// simulated events
				case "button":
					handleButtonPress(FEMALE)
				case "contact":
					handleWireContact(nil)
				case "start":
					handleStartContact(nil)
				case "finish":
					handleFinishContact(nil)
				}
			}
		}()

	}

	// we need to define a robot to be used with gobot
	var robot *gobot.Robot

	// switch cases depending on the mode
	if Mode == MODE_DEBUG {
		// debug mode is run on the mac without physical connections
		fmt.Println("RUNNING IN DEBUG-MODE")

		robot = gobot.NewRobot("buzzwire",
			[]gobot.Connection{},
			[]gobot.Device{},
			work)
	} else {
		// all other modes are run on the pi with physical connections
		robot = gobot.NewRobot("buzzwire",
			[]gobot.Connection{raspberry},
			[]gobot.Device{buttonWoman, ledWoman, contactWire, buzzer, contactFinish},
			work)
	}

	// add the robot to the fleet
	gbot.AddRobot(robot)

	// start the webserver in a separate go routine
	go startServer("localhost:8484")

	// start the robot (blocking)
	gbot.Start()
}
<|endoftext|>


<commit_before>package bookstore

import (
	"fmt"
	"net/http"
)

func init() {
	http.HandleFunc("/", HomeHandler)
}

// HomeHandler handles the home page
func HomeHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprint(w, "Hello, world!")
}
<commit_msg>Prepare to scrape<commit_after>package bookstore

import (
	"fmt"
	"net/http"

	"github.com/yhat/scrape"
	"golang.org/x/net/html"
	"google.golang.org/appengine"
	"google.golang.org/appengine/user"
)

// Match is a result returned from scraping
type Match struct {
	CourseCode string // PHYS-1030-FA
	Synonym    string // 643369
	Title      string // Intro Appl Phys I (Mechanics)
	Instructor string // Dr. Mark C. Gallagher
	Books      string // Link?
	Term       string // Fall
	Department string // Physics
	YearLevel  string // 1
}

func init() {
	http.HandleFunc("/", auth)
}

// HomeHandler handles the home page
func HomeHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprint(w, "Hello, world!")
}

func auth(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)
	u := user.Current(c)
	if u == nil {
		url, err := user.LoginURL(c, r.URL.String())
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		w.Header().Set("Location", url)
		w.WriteHeader(http.StatusFound)
		return
	}
	fmt.Fprintf(w, "Hello, %v!", u)
}

// Scrape finds and serializes the data from Lakehead's
// site.
func Scrape(url string) []*Match {
	return nil
}
<|endoftext|>


<commit_before>package menu

import (
	gltext "github.com/4ydx/gltext"
	glfw "github.com/go-gl/glfw3"
	"github.com/go-gl/glow/gl-core/3.3/gl"
	"github.com/go-gl/mathgl/mgl32"
	"os"
)

type Point struct {
	X, Y float32
}

var vertexShaderSource string = `
#version 330

uniform mat4 matrix;

in vec4 position;

void main() {
  gl_Position = matrix * position;
}
` + "\x00"

var fragmentShaderSource string = `
#version 330

uniform vec4 background;
out vec4 fragment_color;

void main() {
  fragment_color = background;
}
` + "\x00"

type MouseClick int

const (
	MouseUnclicked MouseClick = iota
	MouseLeft
	MouseRight
	MouseCenter
)

type Menu struct {
	//trigger
	OnShow func()

	// options
	Visible      bool
	ShowOn       glfw.Key
	Height       float32
	Width        float32
	IsAutoCenter bool
	lowerLeft    Point

	backgroundUniform int32
	Background        mgl32.Vec4

	// interactive objects
	Font          *gltext.Font
	Labels        []*Label
	TextScaleRate float32 // increment during a scale operation

	// opengl oriented
	WindowWidth   float32
	WindowHeight  float32
	program       uint32 // shader program
	glMatrix      int32  // ortho matrix
	position      uint32 // index location
	vao           uint32
	vbo           uint32
	ebo           uint32
	ortho         mgl32.Mat4
	vboData       []float32
	vboIndexCount int
	eboData       []int32
	eboIndexCount int
}

func (menu *Menu) AddLabel(label *Label, str string) {
	label.Load(menu, menu.Font)
	label.Text.SetString(str)
	label.Text.SetScale(1)
	label.Text.SetPosition(0, 0)
	label.Text.SetColor(0, 0, 0)
	menu.Labels = append(menu.Labels, label)
}

func (menu *Menu) Show() {
	for i := range menu.Labels {
		menu.Labels[i].Reset()
	}
	if menu.OnShow != nil {
		menu.OnShow()
	}
	menu.Visible = true
}

func (menu *Menu) Hide() {
	for i := range menu.Labels {
		menu.Labels[i].Reset()
	}
	menu.Visible = false
}

func (menu *Menu) Toggle() {
	for i := range menu.Labels {
		menu.Labels[i].Reset()
	}
	menu.Visible = !menu.Visible
}

func (menu *Menu) Load(width float32, height float32, scale int32) (err error) {
	glfloat_size := 4
	glint_size := 4

	menu.Visible = false
	menu.ShowOn = glfw.KeyM
	menu.Width = width
	menu.Height = height

	// load font
	fd, err := os.Open("font/luximr.ttf")
	if err != nil {
		return
	}
	defer fd.Close()

	menu.Font, err = gltext.LoadTruetype(fd, scale, 32, 127)
	if err != nil {
		return
	}

	// 2DO: make this time dependent rather than fps dependent
	menu.TextScaleRate = 0.01

	// create shader program and define attributes and uniforms
	menu.program, err = gltext.NewProgram(vertexShaderSource, fragmentShaderSource)
	if err != nil {
		return
	}
	menu.glMatrix = gl.GetUniformLocation(menu.program, gl.Str("matrix\x00"))
	menu.backgroundUniform = gl.GetUniformLocation(menu.program, gl.Str("background\x00"))
	menu.position = uint32(gl.GetAttribLocation(menu.program, gl.Str("position\x00")))

	gl.GenVertexArrays(1, &menu.vao)
	gl.GenBuffers(1, &menu.vbo)
	gl.GenBuffers(1, &menu.ebo)

	// vao
	gl.BindVertexArray(menu.vao)

	// 2DO: Change text depth to get it to render? For now this works.
	gl.Enable(gl.DEPTH_TEST)
	gl.DepthFunc(gl.LEQUAL)

	// vbo
	// specify the buffer for which the VertexAttribPointer calls apply
	gl.BindBuffer(gl.ARRAY_BUFFER, menu.vbo)

	gl.EnableVertexAttribArray(menu.position)
	gl.VertexAttribPointer(
		menu.position,
		2,
		gl.FLOAT,
		false,
		0, // no stride... yet
		gl.PtrOffset(0),
	)

	// ebo
	gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, menu.ebo)

	// i am guessing that order is important here
	gl.BindVertexArray(0)
	gl.BindBuffer(gl.ARRAY_BUFFER, 0)
	gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, 0)

	// ebo, vbo data
	menu.vboIndexCount = 4 * 2 // four indices (2 points per index)
	menu.eboIndexCount = 6     // 6 triangle indices for a quad
	menu.vboData = make([]float32, menu.vboIndexCount, menu.vboIndexCount)
	menu.eboData = make([]int32, menu.eboIndexCount, menu.eboIndexCount)
	menu.lowerLeft = menu.findCenter()
	menu.makeBufferData()

	// setup context
	gl.BindVertexArray(menu.vao)
	gl.BindBuffer(gl.ARRAY_BUFFER, menu.vbo)
	gl.BufferData(
		gl.ARRAY_BUFFER, glfloat_size*menu.vboIndexCount, gl.Ptr(menu.vboData), gl.DYNAMIC_DRAW)
	gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, menu.ebo)
	gl.BufferData(
		gl.ELEMENT_ARRAY_BUFFER, glint_size*menu.eboIndexCount, gl.Ptr(menu.eboData), gl.DYNAMIC_DRAW)
	gl.BindVertexArray(0)

	// not necesssary, but i just want to better understand using vertex arrays
	gl.BindBuffer(gl.ARRAY_BUFFER, 0)
	gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, 0)
	return nil
}

func (menu *Menu) ResizeWindow(width float32, height float32) {
	menu.WindowWidth = width
	menu.WindowHeight = height
	menu.Font.ResizeWindow(width, height)
	menu.ortho = mgl32.Ortho2D(-menu.WindowWidth/2, menu.WindowWidth/2, -menu.WindowHeight/2, menu.WindowHeight/2)
}

func (menu *Menu) makeBufferData() {
	// index (0,0)
	menu.vboData[0] = menu.lowerLeft.X // position
	menu.vboData[1] = menu.lowerLeft.Y

	// index (1,0)
	menu.vboData[2] = menu.lowerLeft.X + menu.Width
	menu.vboData[3] = menu.lowerLeft.Y

	// index (1,1)
	menu.vboData[4] = menu.lowerLeft.X + menu.Width
	menu.vboData[5] = menu.lowerLeft.Y + menu.Height

	// index (0,1)
	menu.vboData[6] = menu.lowerLeft.X
	menu.vboData[7] = menu.lowerLeft.Y + menu.Height

	menu.eboData[0] = 0
	menu.eboData[1] = 1
	menu.eboData[2] = 2
	menu.eboData[3] = 0
	menu.eboData[4] = 2
	menu.eboData[5] = 3
}

func (menu *Menu) Release() {
	gl.DeleteBuffers(1, &menu.vbo)
	gl.DeleteBuffers(1, &menu.ebo)
	gl.DeleteBuffers(1, &menu.vao)
	for i := range menu.Labels {
		menu.Labels[i].Text.Release()
		if menu.Labels[i].Shadow != nil && menu.Labels[i].Shadow.Text != nil {
			menu.Labels[i].Shadow.Text.Release()
		}
	}
}

func (menu *Menu) Draw() bool {
	if !menu.Visible {
		return menu.Visible
	}
	gl.UseProgram(menu.program)

	gl.UniformMatrix4fv(menu.glMatrix, 1, false, &menu.ortho[0])
	gl.Uniform4fv(menu.backgroundUniform, 1, &menu.Background[0])

	gl.BindVertexArray(menu.vao)
	gl.DrawElements(gl.TRIANGLES, int32(menu.eboIndexCount), gl.UNSIGNED_INT, nil)
	gl.BindVertexArray(0)
	for i := range menu.Labels {
		if !menu.Labels[i].IsHover {
			if menu.Labels[i].OnNotHover != nil {
				menu.Labels[i].OnNotHover(menu.Labels[i])
				if menu.Labels[i].Shadow != nil {
					menu.Labels[i].OnNotHover(&menu.Labels[i].Shadow.Label)
				}
			}
		}
		menu.Labels[i].Draw()
	}
	return menu.Visible
}

func (menu *Menu) OrthoToMouseCoord() (x, y float32) {
	x = menu.lowerLeft.X + menu.WindowWidth/2
	y = menu.lowerLeft.Y + menu.WindowHeight/2
	return
}

func (menu *Menu) MouseClick(xPos, yPos float64, button MouseClick) {
	if !menu.Visible {
		return
	}
	yPos = float64(menu.WindowHeight) - yPos
	for i := range menu.Labels {
		if menu.Labels[i].OnClick != nil {
			menu.Labels[i].IsClicked(xPos, yPos, button)
		}
	}
}

func (menu *Menu) MouseRelease(xPos, yPos float64, button MouseClick) {
	if !menu.Visible {
		return
	}
	yPos = float64(menu.WindowHeight) - yPos
	for i := range menu.Labels {
		if menu.Labels[i].OnRelease != nil {
			menu.Labels[i].IsReleased(xPos, yPos, button)
		}
	}
}

func (menu *Menu) MouseHover(xPos, yPos float64) {
	if !menu.Visible {
		return
	}
	yPos = float64(menu.WindowHeight) - yPos
	for i := range menu.Labels {
		if menu.Labels[i].OnHover != nil {
			menu.Labels[i].IsHovered(xPos, yPos)
		}
	}
}

func (menu *Menu) findCenter() (lowerLeft Point) {
	menuWidthHalf := menu.Width / 2
	menuHeightHalf := menu.Height / 2

	lowerLeft.X = -menuWidthHalf
	lowerLeft.Y = -menuHeightHalf
	return
}
<commit_msg>Perhaps the OnShow call wants to hide itself.<commit_after>package menu

import (
	gltext "github.com/4ydx/gltext"
	glfw "github.com/go-gl/glfw3"
	"github.com/go-gl/glow/gl-core/3.3/gl"
	"github.com/go-gl/mathgl/mgl32"
	"os"
)

type Point struct {
	X, Y float32
}

var vertexShaderSource string = `
#version 330

uniform mat4 matrix;

in vec4 position;

void main() {
  gl_Position = matrix * position;
}
` + "\x00"

var fragmentShaderSource string = `
#version 330

uniform vec4 background;
out vec4 fragment_color;

void main() {
  fragment_color = background;
}
` + "\x00"

type MouseClick int

const (
	MouseUnclicked MouseClick = iota
	MouseLeft
	MouseRight
	MouseCenter
)

type Menu struct {
	//trigger
	OnShow func()

	// options
	Visible      bool
	ShowOn       glfw.Key
	Height       float32
	Width        float32
	IsAutoCenter bool
	lowerLeft    Point

	backgroundUniform int32
	Background        mgl32.Vec4

	// interactive objects
	Font          *gltext.Font
	Labels        []*Label
	TextScaleRate float32 // increment during a scale operation

	// opengl oriented
	WindowWidth   float32
	WindowHeight  float32
	program       uint32 // shader program
	glMatrix      int32  // ortho matrix
	position      uint32 // index location
	vao           uint32
	vbo           uint32
	ebo           uint32
	ortho         mgl32.Mat4
	vboData       []float32
	vboIndexCount int
	eboData       []int32
	eboIndexCount int
}

func (menu *Menu) AddLabel(label *Label, str string) {
	label.Load(menu, menu.Font)
	label.Text.SetString(str)
	label.Text.SetScale(1)
	label.Text.SetPosition(0, 0)
	label.Text.SetColor(0, 0, 0)
	menu.Labels = append(menu.Labels, label)
}

func (menu *Menu) Show() {
	for i := range menu.Labels {
		menu.Labels[i].Reset()
	}
	menu.Visible = true
	if menu.OnShow != nil {
		menu.OnShow()
	}
}

func (menu *Menu) Hide() {
	for i := range menu.Labels {
		menu.Labels[i].Reset()
	}
	menu.Visible = false
}

func (menu *Menu) Toggle() {
	for i := range menu.Labels {
		menu.Labels[i].Reset()
	}
	menu.Visible = !menu.Visible
}

func (menu *Menu) Load(width float32, height float32, scale int32) (err error) {
	glfloat_size := 4
	glint_size := 4

	menu.Visible = false
	menu.ShowOn = glfw.KeyM
	menu.Width = width
	menu.Height = height

	// load font
	fd, err := os.Open("font/luximr.ttf")
	if err != nil {
		return
	}
	defer fd.Close()

	menu.Font, err = gltext.LoadTruetype(fd, scale, 32, 127)
	if err != nil {
		return
	}

	// 2DO: make this time dependent rather than fps dependent
	menu.TextScaleRate = 0.01

	// create shader program and define attributes and uniforms
	menu.program, err = gltext.NewProgram(vertexShaderSource, fragmentShaderSource)
	if err != nil {
		return
	}
	menu.glMatrix = gl.GetUniformLocation(menu.program, gl.Str("matrix\x00"))
	menu.backgroundUniform = gl.GetUniformLocation(menu.program, gl.Str("background\x00"))
	menu.position = uint32(gl.GetAttribLocation(menu.program, gl.Str("position\x00")))

	gl.GenVertexArrays(1, &menu.vao)
	gl.GenBuffers(1, &menu.vbo)
	gl.GenBuffers(1, &menu.ebo)

	// vao
	gl.BindVertexArray(menu.vao)

	// 2DO: Change text depth to get it to render? For now this works.
	gl.Enable(gl.DEPTH_TEST)
	gl.DepthFunc(gl.LEQUAL)

	// vbo
	// specify the buffer for which the VertexAttribPointer calls apply
	gl.BindBuffer(gl.ARRAY_BUFFER, menu.vbo)

	gl.EnableVertexAttribArray(menu.position)
	gl.VertexAttribPointer(
		menu.position,
		2,
		gl.FLOAT,
		false,
		0, // no stride... yet
		gl.PtrOffset(0),
	)

	// ebo
	gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, menu.ebo)

	// i am guessing that order is important here
	gl.BindVertexArray(0)
	gl.BindBuffer(gl.ARRAY_BUFFER, 0)
	gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, 0)

	// ebo, vbo data
	menu.vboIndexCount = 4 * 2 // four indices (2 points per index)
	menu.eboIndexCount = 6     // 6 triangle indices for a quad
	menu.vboData = make([]float32, menu.vboIndexCount, menu.vboIndexCount)
	menu.eboData = make([]int32, menu.eboIndexCount, menu.eboIndexCount)
	menu.lowerLeft = menu.findCenter()
	menu.makeBufferData()

	// setup context
	gl.BindVertexArray(menu.vao)
	gl.BindBuffer(gl.ARRAY_BUFFER, menu.vbo)
	gl.BufferData(
		gl.ARRAY_BUFFER, glfloat_size*menu.vboIndexCount, gl.Ptr(menu.vboData), gl.DYNAMIC_DRAW)
	gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, menu.ebo)
	gl.BufferData(
		gl.ELEMENT_ARRAY_BUFFER, glint_size*menu.eboIndexCount, gl.Ptr(menu.eboData), gl.DYNAMIC_DRAW)
	gl.BindVertexArray(0)

	// not necesssary, but i just want to better understand using vertex arrays
	gl.BindBuffer(gl.ARRAY_BUFFER, 0)
	gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, 0)
	return nil
}

func (menu *Menu) ResizeWindow(width float32, height float32) {
	menu.WindowWidth = width
	menu.WindowHeight = height
	menu.Font.ResizeWindow(width, height)
	menu.ortho = mgl32.Ortho2D(-menu.WindowWidth/2, menu.WindowWidth/2, -menu.WindowHeight/2, menu.WindowHeight/2)
}

func (menu *Menu) makeBufferData() {
	// index (0,0)
	menu.vboData[0] = menu.lowerLeft.X // position
	menu.vboData[1] = menu.lowerLeft.Y

	// index (1,0)
	menu.vboData[2] = menu.lowerLeft.X + menu.Width
	menu.vboData[3] = menu.lowerLeft.Y

	// index (1,1)
	menu.vboData[4] = menu.lowerLeft.X + menu.Width
	menu.vboData[5] = menu.lowerLeft.Y + menu.Height

	// index (0,1)
	menu.vboData[6] = menu.lowerLeft.X
	menu.vboData[7] = menu.lowerLeft.Y + menu.Height

	menu.eboData[0] = 0
	menu.eboData[1] = 1
	menu.eboData[2] = 2
	menu.eboData[3] = 0
	menu.eboData[4] = 2
	menu.eboData[5] = 3
}

func (menu *Menu) Release() {
	gl.DeleteBuffers(1, &menu.vbo)
	gl.DeleteBuffers(1, &menu.ebo)
	gl.DeleteBuffers(1, &menu.vao)
	for i := range menu.Labels {
		menu.Labels[i].Text.Release()
		if menu.Labels[i].Shadow != nil && menu.Labels[i].Shadow.Text != nil {
			menu.Labels[i].Shadow.Text.Release()
		}
	}
}

func (menu *Menu) Draw() bool {
	if !menu.Visible {
		return menu.Visible
	}
	gl.UseProgram(menu.program)

	gl.UniformMatrix4fv(menu.glMatrix, 1, false, &menu.ortho[0])
	gl.Uniform4fv(menu.backgroundUniform, 1, &menu.Background[0])

	gl.BindVertexArray(menu.vao)
	gl.DrawElements(gl.TRIANGLES, int32(menu.eboIndexCount), gl.UNSIGNED_INT, nil)
	gl.BindVertexArray(0)
	for i := range menu.Labels {
		if !menu.Labels[i].IsHover {
			if menu.Labels[i].OnNotHover != nil {
				menu.Labels[i].OnNotHover(menu.Labels[i])
				if menu.Labels[i].Shadow != nil {
					menu.Labels[i].OnNotHover(&menu.Labels[i].Shadow.Label)
				}
			}
		}
		menu.Labels[i].Draw()
	}
	return menu.Visible
}

func (menu *Menu) OrthoToMouseCoord() (x, y float32) {
	x = menu.lowerLeft.X + menu.WindowWidth/2
	y = menu.lowerLeft.Y + menu.WindowHeight/2
	return
}

func (menu *Menu) MouseClick(xPos, yPos float64, button MouseClick) {
	if !menu.Visible {
		return
	}
	yPos = float64(menu.WindowHeight) - yPos
	for i := range menu.Labels {
		if menu.Labels[i].OnClick != nil {
			menu.Labels[i].IsClicked(xPos, yPos, button)
		}
	}
}

func (menu *Menu) MouseRelease(xPos, yPos float64, button MouseClick) {
	if !menu.Visible {
		return
	}
	yPos = float64(menu.WindowHeight) - yPos
	for i := range menu.Labels {
		if menu.Labels[i].OnRelease != nil {
			menu.Labels[i].IsReleased(xPos, yPos, button)
		}
	}
}

func (menu *Menu) MouseHover(xPos, yPos float64) {
	if !menu.Visible {
		return
	}
	yPos = float64(menu.WindowHeight) - yPos
	for i := range menu.Labels {
		if menu.Labels[i].OnHover != nil {
			menu.Labels[i].IsHovered(xPos, yPos)
		}
	}
}

func (menu *Menu) findCenter() (lowerLeft Point) {
	menuWidthHalf := menu.Width / 2
	menuHeightHalf := menu.Height / 2

	lowerLeft.X = -menuWidthHalf
	lowerLeft.Y = -menuHeightHalf
	return
}
<|endoftext|>


<commit_before>package wmenu

//TODO add wlog color features to this
//TODO have a way to clear screen on new menu or not
import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"

	"github.com/dixonwille/wlog"
)

//Menu is the structure for all menus created
type Menu struct {
	question        string
	defaultFunction func(Option)
	options         []Option
	ui              wlog.UI
	multiSeperator  string
	multiFunction   func([]Option)
	loopOnInvalid   bool
	clear           bool
}

//NewMenu creates a menu to use
func NewMenu(question string) *Menu {
	//Create a default ui to use for menu
	var ui wlog.UI
	ui = wlog.New(os.Stdin, os.Stdout, os.Stderr)
	ui = wlog.AddConcurrent(ui)

	return &Menu{
		question:        question,
		defaultFunction: nil,
		options:         nil,
		ui:              ui,
		multiSeperator:  " ",
		multiFunction:   nil,
		loopOnInvalid:   false,
		clear:           false,
	}
}

//AddColor will change the color of the menu items.
//optionColor changes the color of the options.
//questionColor changes the color of the questions.
//errorColor changes the color of the question.
//Use wlog.None if you do not want to change the color.
func (m *Menu) AddColor(optionColor, questionColor, errorColor wlog.Color) {
	m.ui = wlog.AddColor(wlog.None, optionColor, wlog.None, questionColor, errorColor, wlog.None, wlog.None, m.ui)
}

//ClearOnMenuRun will clear the screen when a menu is ran.
func (m *Menu) ClearOnMenuRun() {
	m.clear = true
}

//SetSeperator sets the seperator to use for multi select.
//Default value is a space.
func (m *Menu) SetSeperator(sep string) {
	m.multiSeperator = sep
}

//LoopOnInvalid is used if an invalid option was given then clear the screen and ask again
func (m *Menu) LoopOnInvalid() {
	m.loopOnInvalid = true
}

//Option adds options to the menu
func (m *Menu) Option(title string, isDefault bool, function func()) {
	option := newOption(len(m.options), title, isDefault, function)
	m.options = append(m.options, *option)
}

//Action adds a default action if an option does not have an action or no option set as default.
func (m *Menu) Action(function func(Option)) {
	m.defaultFunction = function
}

//MultipleAction is called when mulitple options are selected.
func (m *Menu) MultipleAction(function func([]Option)) {
	m.multiFunction = function
}

//Run is used to execute the menu (print to screen and ask quesiton)
func (m *Menu) Run() error {
	if m.clear {
		Clear()
	}
	valid := false
	var options []Option
	//Loop and on error check if loopOnInvalid is enabled.
	//If it is Clear the screen and write error.
	//Then ask again
	for !valid {
		//step 1 print things to screen
		m.print()
		//step 2 get and validate response
		opt, err := m.ask()
		if err != nil {
			if m.loopOnInvalid {
				if m.clear {
					Clear()
				}
				m.ui.Error(err.Error())
			} else {
				return err
			}
		} else {
			options = opt
			valid = true
		}
	}
	//step 3 call appropriate action with the responses
	switch len(options) {
	//if no options go through options and look for default options
	case 0:
		opt := m.getDefault()
		switch len(opt) {
		//if there are no default options call the defaultFunction of the menu
		case 0:
			m.defaultFunction(Option{id: -1})
			//if there is one default option call it's function if it exist
			//if it does not, call the menu's defaultFunction
		case 1:
			if opt[0].function == nil {
				m.defaultFunction(opt[0])
			} else {
				opt[0].function()
			}
			//if there is more than one default option call the menu's multiFunction
		default:
			m.multiFunction(opt)
		}
		//if there is one option call it's funciton if it exist
		//if it does not, call the menu's defaultFunction
	case 1:
		if options[0].function == nil {
			m.defaultFunction(options[0])
		} else {
			options[0].function()
		}
		//if there is more than one option call the menu's multiFunction
	default:
		m.multiFunction(options)
	}
	return nil
}

func (m *Menu) print() {
	for _, opt := range m.options {
		m.ui.Output(fmt.Sprintf("%d) %s", opt.id, opt.text))
	}
	m.ui.Info(m.question)
}

func (m *Menu) ask() ([]Option, error) {
	reader := bufio.NewReader(os.Stdin)
	res, _ := reader.ReadString('\n')
	res = strings.Replace(res, "\r", "", -1) //this will only be useful under windows
	res = strings.Replace(res, "\n", "", -1)

	//Validate responses
	//Check if no responses are returned and no action to call
	if res == "" {
		//get default options
		opt := m.getDefault()
		//make sure that there is an action available to be called in certain cases
		isErr := ((len(opt) == 0 && m.defaultFunction == nil) || (len(opt) == 1 && opt[0].function == nil && m.defaultFunction == nil) || (len(opt) > 0 && m.multiFunction == nil))
		if isErr {
			return nil, newMenuError(ErrNoResponse, "")
		}
		return nil, nil
	}

	resStrings := strings.Split(res, m.multiSeperator) //split responses by spaces
	//Check if we don't want multiple responses
	if m.multiFunction == nil && len(resStrings) > 1 {
		return nil, newMenuError(ErrTooMany, "")
	}

	//Convert responses to intigers
	var responses []int
	for _, response := range resStrings {
		//Check if it is an intiger
		r, err := strconv.Atoi(response)
		if err != nil {
			return nil, newMenuError(ErrInvalid, response)
		}
		responses = append(responses, r)
	}

	//Check if response is in the range of options
	//If it is make sure it is not duplicated
	var tmp []int
	for _, response := range responses {
		if response < 0 || len(m.options)-1 < response {
			return nil, newMenuError(ErrInvalid, strconv.Itoa(response))
		}

		if exist(tmp, response) {
			return nil, newMenuError(ErrDuplicate, strconv.Itoa(response))
		}

		tmp = append(tmp, response)
	}

	//Parse responses and return them as options
	var finalOptions []Option
	for _, response := range responses {
		finalOptions = append(finalOptions, m.options[response])
	}

	return finalOptions, nil
}

//Simply checks if number exists in the slice
func exist(slice []int, number int) bool {
	for _, s := range slice {
		if number == s {
			return true
		}
	}
	return false
}

func (m *Menu) getDefault() []Option {
	var opt []Option
	for _, o := range m.options {
		if o.isDefault {
			opt = append(opt, o)
		}
	}
	return opt
}
<commit_msg>Removed todos<commit_after>package wmenu

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"

	"github.com/dixonwille/wlog"
)

//Menu is the structure for all menus created
type Menu struct {
	question        string
	defaultFunction func(Option)
	options         []Option
	ui              wlog.UI
	multiSeperator  string
	multiFunction   func([]Option)
	loopOnInvalid   bool
	clear           bool
}

//NewMenu creates a menu to use
func NewMenu(question string) *Menu {
	//Create a default ui to use for menu
	var ui wlog.UI
	ui = wlog.New(os.Stdin, os.Stdout, os.Stderr)
	ui = wlog.AddConcurrent(ui)

	return &Menu{
		question:        question,
		defaultFunction: nil,
		options:         nil,
		ui:              ui,
		multiSeperator:  " ",
		multiFunction:   nil,
		loopOnInvalid:   false,
		clear:           false,
	}
}

//AddColor will change the color of the menu items.
//optionColor changes the color of the options.
//questionColor changes the color of the questions.
//errorColor changes the color of the question.
//Use wlog.None if you do not want to change the color.
func (m *Menu) AddColor(optionColor, questionColor, errorColor wlog.Color) {
	m.ui = wlog.AddColor(wlog.None, optionColor, wlog.None, questionColor, errorColor, wlog.None, wlog.None, m.ui)
}

//ClearOnMenuRun will clear the screen when a menu is ran.
func (m *Menu) ClearOnMenuRun() {
	m.clear = true
}

//SetSeperator sets the seperator to use for multi select.
//Default value is a space.
func (m *Menu) SetSeperator(sep string) {
	m.multiSeperator = sep
}

//LoopOnInvalid is used if an invalid option was given then clear the screen and ask again
func (m *Menu) LoopOnInvalid() {
	m.loopOnInvalid = true
}

//Option adds options to the menu
func (m *Menu) Option(title string, isDefault bool, function func()) {
	option := newOption(len(m.options), title, isDefault, function)
	m.options = append(m.options, *option)
}

//Action adds a default action if an option does not have an action or no option set as default.
func (m *Menu) Action(function func(Option)) {
	m.defaultFunction = function
}

//MultipleAction is called when mulitple options are selected.
func (m *Menu) MultipleAction(function func([]Option)) {
	m.multiFunction = function
}

//Run is used to execute the menu (print to screen and ask quesiton)
func (m *Menu) Run() error {
	if m.clear {
		Clear()
	}
	valid := false
	var options []Option
	//Loop and on error check if loopOnInvalid is enabled.
	//If it is Clear the screen and write error.
	//Then ask again
	for !valid {
		//step 1 print things to screen
		m.print()
		//step 2 get and validate response
		opt, err := m.ask()
		if err != nil {
			if m.loopOnInvalid {
				if m.clear {
					Clear()
				}
				m.ui.Error(err.Error())
			} else {
				return err
			}
		} else {
			options = opt
			valid = true
		}
	}
	//step 3 call appropriate action with the responses
	switch len(options) {
	//if no options go through options and look for default options
	case 0:
		opt := m.getDefault()
		switch len(opt) {
		//if there are no default options call the defaultFunction of the menu
		case 0:
			m.defaultFunction(Option{id: -1})
			//if there is one default option call it's function if it exist
			//if it does not, call the menu's defaultFunction
		case 1:
			if opt[0].function == nil {
				m.defaultFunction(opt[0])
			} else {
				opt[0].function()
			}
			//if there is more than one default option call the menu's multiFunction
		default:
			m.multiFunction(opt)
		}
		//if there is one option call it's funciton if it exist
		//if it does not, call the menu's defaultFunction
	case 1:
		if options[0].function == nil {
			m.defaultFunction(options[0])
		} else {
			options[0].function()
		}
		//if there is more than one option call the menu's multiFunction
	default:
		m.multiFunction(options)
	}
	return nil
}

func (m *Menu) print() {
	for _, opt := range m.options {
		m.ui.Output(fmt.Sprintf("%d) %s", opt.id, opt.text))
	}
	m.ui.Info(m.question)
}

func (m *Menu) ask() ([]Option, error) {
	reader := bufio.NewReader(os.Stdin)
	res, _ := reader.ReadString('\n')
	res = strings.Replace(res, "\r", "", -1) //this will only be useful under windows
	res = strings.Replace(res, "\n", "", -1)

	//Validate responses
	//Check if no responses are returned and no action to call
	if res == "" {
		//get default options
		opt := m.getDefault()
		//make sure that there is an action available to be called in certain cases
		isErr := ((len(opt) == 0 && m.defaultFunction == nil) || (len(opt) == 1 && opt[0].function == nil && m.defaultFunction == nil) || (len(opt) > 0 && m.multiFunction == nil))
		if isErr {
			return nil, newMenuError(ErrNoResponse, "")
		}
		return nil, nil
	}

	resStrings := strings.Split(res, m.multiSeperator) //split responses by spaces
	//Check if we don't want multiple responses
	if m.multiFunction == nil && len(resStrings) > 1 {
		return nil, newMenuError(ErrTooMany, "")
	}

	//Convert responses to intigers
	var responses []int
	for _, response := range resStrings {
		//Check if it is an intiger
		r, err := strconv.Atoi(response)
		if err != nil {
			return nil, newMenuError(ErrInvalid, response)
		}
		responses = append(responses, r)
	}

	//Check if response is in the range of options
	//If it is make sure it is not duplicated
	var tmp []int
	for _, response := range responses {
		if response < 0 || len(m.options)-1 < response {
			return nil, newMenuError(ErrInvalid, strconv.Itoa(response))
		}

		if exist(tmp, response) {
			return nil, newMenuError(ErrDuplicate, strconv.Itoa(response))
		}

		tmp = append(tmp, response)
	}

	//Parse responses and return them as options
	var finalOptions []Option
	for _, response := range responses {
		finalOptions = append(finalOptions, m.options[response])
	}

	return finalOptions, nil
}

//Simply checks if number exists in the slice
func exist(slice []int, number int) bool {
	for _, s := range slice {
		if number == s {
			return true
		}
	}
	return false
}

func (m *Menu) getDefault() []Option {
	var opt []Option
	for _, o := range m.options {
		if o.isDefault {
			opt = append(opt, o)
		}
	}
	return opt
}
<|endoftext|>


<commit_before>// Package mime helps retrieving mimetypes given extensions.
// This is an alternative to the "mime" package, and has fallbacks for the most common types.
package mime

import (
	"bytes"
	"io/ioutil"
	"net/http"
	"sync"
)

var fallback = map[string]string{
	"tar.gz":  "application/x-gzip-compressed-tar",
	"tar.bz":  "application/x-bzip-compressed-tar",
	"tar.bz2": "application/x-bzip-compressed-tar",
	"tar.xz":  "application/x-xz-compressed-tar",
	"tgz":     "application/x-gzip-compressed-tar",
	"tbz":     "application/x-bzip-compressed-tar",
	"tbz2":    "application/x-bzip-compressed-tar",
	"txz":     "application/x-xz-compressed-tar",
	"gz":      "application/x-gzip",
	"bz2":     "application/x-bzip2",
	"xz":      "application/x-xz",
	"html":    "text/html",
	"css":     "text/css",
	"js":      "application/javascript",
	"txt":     "text/plain",
	"png":     "image/png",
	"jpg":     "image/jpg",
	"json":    "application/javascript",
	"svg":     "image/svg+xml",
	"xml":     "text/xml",
	"rss":     "application/rss+xml",
	"zip":     "application/zip",
	"tar":     "application/x-tar",
}

// Reader caches the contents of a mime info text file
type Reader struct {
	filename  string
	utf8      bool
	mimetypes map[string]string
	mu        sync.Mutex
}

// New creates a new Reader. The filename is a list of mimetypes and extensions.
// If utf8 is true, "; charset=utf-8" will be added when setting http headers.
func New(filename string, utf8 bool) *Reader {
	return &Reader{filename, utf8, nil, sync.Mutex{}}
}

// Read a mimetype text file. Return a hash map from ext to mimetype.
func readMimetypes(filename string) (map[string]string, error) {
	mimetypes := make(map[string]string)
	// Read the mimetype file
	data, err := ioutil.ReadFile(filename)
	if err != nil {
		return nil, err
	}
	// For each line, store extensions and mimetypes in the hash map
	for _, line := range bytes.Split(data, []byte("\n")) {
		fields := bytes.Fields(line)
		if len(fields) > 1 {
			for _, ext := range fields[1:] {
				mimetypes[string(ext)] = string(fields[0])
			}
		}
	}
	return mimetypes, nil
}

// Get returns the mimetype, or an empty string if no mimetype or mimetype source is found
func (mr *Reader) Get(ext string) string {
	var err error
	// No extension, suggest text/plain (README, LICENSE etc)
	if len(ext) == 0 {
		return "text/plain"
	}
	// Strip the leading dot
	if ext[0] == '.' {
		ext = ext[1:]
	}
	mr.mu.Lock()
	defer mr.mu.Unlock()
	if mr.mimetypes == nil {
		mr.mimetypes, err = readMimetypes(mr.filename)
		if err != nil {
			// Using the fallback hash map
			if mime, ok := fallback[ext]; ok {
				return mime
			}
			// Unable to find the mime type for the given extension
			return ""
		}
	}
	// Use the value from the hash map
	if mime, ok := mr.mimetypes[ext]; ok {
		return mime
	}
	// Using the fallback hash map
	if mime, ok := fallback[ext]; ok {
		return mime
	}
	// Unable to find the mime type for the given extension
	return ""
}

// SetHeader sets the Content-Type for a given ResponseWriter and filename extension
func (mr *Reader) SetHeader(w http.ResponseWriter, ext string) {
	mimestring := mr.Get(ext)
	if mimestring == "" {
		// Default mime type
		mimestring = "application/octet-stream"
	}
	if mr.utf8 {
		mimestring += "; charset=utf-8"
	}
	w.Header().Add("Content-Type", mimestring)
}
<commit_msg>Don't assume text/plain for files without an extension<commit_after>// Package mime helps retrieving mimetypes given extensions.
// This is an alternative to the "mime" package, and has fallbacks for the most common types.
package mime

import (
	"bytes"
	"io/ioutil"
	"net/http"
	"sync"
)

var fallback = map[string]string{
	"tar.gz":  "application/x-gzip-compressed-tar",
	"tar.bz":  "application/x-bzip-compressed-tar",
	"tar.bz2": "application/x-bzip-compressed-tar",
	"tar.xz":  "application/x-xz-compressed-tar",
	"tgz":     "application/x-gzip-compressed-tar",
	"tbz":     "application/x-bzip-compressed-tar",
	"tbz2":    "application/x-bzip-compressed-tar",
	"txz":     "application/x-xz-compressed-tar",
	"gz":      "application/x-gzip",
	"bz2":     "application/x-bzip2",
	"xz":      "application/x-xz",
	"html":    "text/html",
	"css":     "text/css",
	"js":      "application/javascript",
	"txt":     "text/plain",
	"png":     "image/png",
	"jpg":     "image/jpg",
	"json":    "application/javascript",
	"svg":     "image/svg+xml",
	"xml":     "text/xml",
	"rss":     "application/rss+xml",
	"zip":     "application/zip",
	"tar":     "application/x-tar",
}

// Reader caches the contents of a mime info text file
type Reader struct {
	filename  string
	utf8      bool
	mimetypes map[string]string
	mu        sync.Mutex
}

// New creates a new Reader. The filename is a list of mimetypes and extensions.
// If utf8 is true, "; charset=utf-8" will be added when setting http headers.
func New(filename string, utf8 bool) *Reader {
	return &Reader{filename, utf8, nil, sync.Mutex{}}
}

// Read a mimetype text file. Return a hash map from ext to mimetype.
func readMimetypes(filename string) (map[string]string, error) {
	mimetypes := make(map[string]string)
	// Read the mimetype file
	data, err := ioutil.ReadFile(filename)
	if err != nil {
		return nil, err
	}
	// For each line, store extensions and mimetypes in the hash map
	for _, line := range bytes.Split(data, []byte("\n")) {
		fields := bytes.Fields(line)
		if len(fields) > 1 {
			for _, ext := range fields[1:] {
				mimetypes[string(ext)] = string(fields[0])
			}
		}
	}
	return mimetypes, nil
}

// Get returns the mimetype, or an empty string if no mimetype or mimetype source is found
func (mr *Reader) Get(ext string) string {
	var err error
	// No extension
	if len(ext) == 0 {
		return ""
	}
	// Strip the leading dot
	if ext[0] == '.' {
		ext = ext[1:]
	}
	mr.mu.Lock()
	defer mr.mu.Unlock()
	if mr.mimetypes == nil {
		mr.mimetypes, err = readMimetypes(mr.filename)
		if err != nil {
			// Using the fallback hash map
			if mime, ok := fallback[ext]; ok {
				return mime
			}
			// Unable to find the mime type for the given extension
			return ""
		}
	}
	// Use the value from the hash map
	if mime, ok := mr.mimetypes[ext]; ok {
		return mime
	}
	// Using the fallback hash map
	if mime, ok := fallback[ext]; ok {
		return mime
	}
	// Unable to find the mime type for the given extension
	return ""
}

// SetHeader sets the Content-Type for a given ResponseWriter and filename extension
func (mr *Reader) SetHeader(w http.ResponseWriter, ext string) {
	mimestring := mr.Get(ext)
	if mimestring == "" {
		// Default mime type
		mimestring = "application/octet-stream"
	}
	if mr.utf8 {
		mimestring += "; charset=utf-8"
	}
	w.Header().Add("Content-Type", mimestring)
}
<|endoftext|>


<commit_before>// Mind is a server that streams numbers.
//
// The server is a generator of numbers, which are sent to a client
// via the websocket protocol. The client, which is some HTML and JS
// returned by visiting the index page, visualizes the stream of numbers.
package main

import (
	"fmt"
	"log"
	"math/rand"
	"net/http"
	"time"

	"github.com/gorilla/websocket"
)

type Mind struct {
	x int
}

const IndexHTML = `
<!DOCTYPE html>

<canvas id="myCanvas" width="800" height="800" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.
</canvas>

`

func g(x int) int {
	//	return int(rand.Float64() * max)
	max := 255
	return int(float64(x)*rand.Float64()+rand.Float64()*50) % max
}

func f(x int) int {
	d := rand.Float64() - rand.Float64()
	return int(float64(x)+d) % 255
}

func (m *Mind) change() {
	m.x = g(m.x)
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
	log.Printf("got request")
	fmt.Fprintf(w, IndexHTML)
}

func wsHandler(w http.ResponseWriter, r *http.Request) {
	log.Printf("got ws request")
	u := websocket.Upgrader{}
	conn, err := u.Upgrade(w, r, http.Header{})
	if err != nil {
		log.Printf("failed to upgrade http request to websocket: %v\n", err)
		return
	}
	log.Println("upgraded to websocket")
	m := Mind{1000}
	i := 0
	msg := ""
	for {
		if i%100 == 0 {
			if i%10000 == 0 {
				log.Println(msg)
			}
			msg = ""
		}
		// time.Sleep(time.Millisecond * 1)
		time.Sleep(time.Nanosecond * 1000 * 10)
		// log.Printf("%+v: %q\n", m, string(m.x*'.'))
		m.change()
		msg += string(m.x * '.')
		msg := []byte(fmt.Sprintf("%d", m.x))
		err = conn.WriteMessage(websocket.TextMessage, msg)
		if err != nil {
			log.Printf("failed to fetch writer from websocket: %v\n", err)
			return
		}
		i += 1
	}
}

func main() {
	log.Println("Connect to websocket to hear your mind")
	http.HandleFunc("/", indexHandler)
	http.HandleFunc("/stream", wsHandler)

	log.Fatal(http.ListenAndServe(":8080", nil))

}
<commit_msg>Check if printable before printing rune<commit_after>// Mind is a server that streams numbers.
//
// The server is a generator of numbers, which are sent to a client
// via the websocket protocol. The client, which is some HTML and JS
// returned by visiting the index page, visualizes the stream of numbers.
package main

import (
	"fmt"
	"log"
	"math/rand"
	"net/http"
	"time"
	"unicode"

	"github.com/gorilla/websocket"
)

type Mind struct {
	x int
}

const IndexHTML = `
<!DOCTYPE html>

<canvas id="myCanvas" width="800" height="800" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.
</canvas>

`

func g(x int) int {
	//	return int(rand.Float64() * max)
	max := 255
	return int(float64(x)*rand.Float64()+rand.Float64()*50) % max
}

func f(x int) int {
	d := rand.Float64() - rand.Float64()
	return int(float64(x)+d) % 255
}

func (m *Mind) change() {
	m.x = g(m.x)
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
	log.Printf("got request")
	fmt.Fprintf(w, IndexHTML)
}

func wsHandler(w http.ResponseWriter, r *http.Request) {
	log.Printf("got ws request")
	u := websocket.Upgrader{}
	conn, err := u.Upgrade(w, r, http.Header{})
	if err != nil {
		log.Printf("failed to upgrade http request to websocket: %v\n", err)
		return
	}
	log.Println("upgraded to websocket")
	m := Mind{1000}
	i := 0
	runes := ""
	for {
		if i%100 == 0 {
			if i%10000 == 0 {
				log.Println(runes)
			}
			runes = ""
		}
		time.Sleep(time.Nanosecond * 1000 * 10)
		m.change()
		r := m.x * '.'
		if unicode.IsPrint(rune(r)) {
			runes += string(r)
		}
		msg := []byte(fmt.Sprintf("%d", m.x))
		err = conn.WriteMessage(websocket.TextMessage, msg)
		if err != nil {
			log.Printf("failed to fetch writer from websocket: %v\n", err)
			return
		}
		i += 1
	}
}

func main() {
	log.Println("Connect to websocket to hear your mind")
	http.HandleFunc("/", indexHandler)
	http.HandleFunc("/stream", wsHandler)

	log.Fatal(http.ListenAndServe(":8080", nil))

}
<|endoftext|>


<commit_before>/*******************************************************************************
The MIT License (MIT)

Copyright (c) 2015 Hajime Nakagami

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

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

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

package main

import (
	"fmt"
	"net"
)

func MqttMainLoop(conn net.Conn) {
	command, _, remaining, err := readMessage(conn)
	if command != CONNECT || err != nil {
		sendToConn(packCONNACK(CONNACK_Rejected), conn)
		return
	}
	clientID, _, _, loginName, loginPassword, err := unpackCONNECT(remaining)
	status := login(clientID, loginName, loginPassword)
	sendToConn(packCONNACK(status), conn)
	if status != CONNACK_Success {
		return
	}

	for {
		command, _, remaining, err = readMessage(conn)
		if err != nil {
			logout(clientID)
			break
		}
		switch command {
		case PUBLISH:
			topic, messageID, payload, err := unpackPUBLISH(remaining)
			debugOutput(fmt.Sprintf("PUBLISH:%s,%d,%v,%v", topic, messageID, payload, err))

			for _, clientID := range getClientListByTopic(topic) {
				messageID = getNextMessageID(clientID)
				data := packPUBLISH(topic, messageID, payload)
				sendToClient(data, clientID)
			}
		case PUBACK:
			debugOutput("PUBACK")
		case PUBREL:
			debugOutput("PUBREL")
		case SUBSCRIBE:
			messageID, subscribe_topics, err := unpackSUBSCRIBE(remaining)
			debugOutput(fmt.Sprintf("SUBSCRIBE:%v,%d,%v", subscribe_topics, messageID, err))
			qos := make([]byte, len(subscribe_topics))
			for i, topic := range subscribe_topics {
				qos[i] = subscribe(topic, clientID)
			}
			sendToConn(packSUBACK(messageID, qos), conn)
		case UNSUBSCRIBE:
			messageID, unsubscribe_topics, err := unpackUNSUBSCRIBE(remaining)
			debugOutput(fmt.Sprintf("UNSUBSCRIBE:%v,%s,%d,%v", unsubscribe_topics, messageID, err))
			for _, topic := range unsubscribe_topics {
				unsubscribe(topic, clientID)
			}
		case PINGREQ:
			debugOutput("PINGREQ")
			sendToConn(packPINGRESP(), conn)
		case DISCONNECT:
			debugOutput("DISCONNECT")
			logout(clientID)
			break
		default:
			debugOutput("Invalid Command")
			logout(clientID)
			break
		}
	}
}
<commit_msg>UNSUBACK<commit_after>/*******************************************************************************
The MIT License (MIT)

Copyright (c) 2015 Hajime Nakagami

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

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

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

package main

import (
	"fmt"
	"net"
)

func MqttMainLoop(conn net.Conn) {
	command, _, remaining, err := readMessage(conn)
	if command != CONNECT || err != nil {
		sendToConn(packCONNACK(CONNACK_Rejected), conn)
		return
	}
	clientID, _, _, loginName, loginPassword, err := unpackCONNECT(remaining)
	status := login(clientID, loginName, loginPassword)
	sendToConn(packCONNACK(status), conn)
	if status != CONNACK_Success {
		return
	}

	for {
		command, _, remaining, err = readMessage(conn)
		if err != nil {
			logout(clientID)
			break
		}
		switch command {
		case PUBLISH:
			topic, messageID, payload, err := unpackPUBLISH(remaining)
			debugOutput(fmt.Sprintf("PUBLISH:%s,%d,%v,%v", topic, messageID, payload, err))

			for _, clientID := range getClientListByTopic(topic) {
				messageID = getNextMessageID(clientID)
				data := packPUBLISH(topic, messageID, payload)
				sendToClient(data, clientID)
			}
		case PUBACK:
			debugOutput("PUBACK")
		case PUBREL:
			debugOutput("PUBREL")
		case SUBSCRIBE:
			messageID, subscribe_topics, err := unpackSUBSCRIBE(remaining)
			debugOutput(fmt.Sprintf("SUBSCRIBE:%v,%d,%v", subscribe_topics, messageID, err))
			qos := make([]byte, len(subscribe_topics))
			for i, topic := range subscribe_topics {
				qos[i] = subscribe(topic, clientID)
			}
			sendToConn(packSUBACK(messageID, qos), conn)
		case UNSUBSCRIBE:
			messageID, unsubscribe_topics, err := unpackUNSUBSCRIBE(remaining)
			debugOutput(fmt.Sprintf("UNSUBSCRIBE:%v,%d,%v", unsubscribe_topics, messageID, err))
			for _, topic := range unsubscribe_topics {
				unsubscribe(topic, clientID)
			}
			sendToConn(packUNSUBACK(messageID), conn)
		case PINGREQ:
			debugOutput("PINGREQ")
			sendToConn(packPINGRESP(), conn)
		case DISCONNECT:
			debugOutput("DISCONNECT")
			logout(clientID)
			break
		default:
			debugOutput("Invalid Command")
			logout(clientID)
			break
		}
	}
}
<|endoftext|>


<commit_before>package sdm630

import (
	"fmt"
	MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git"
)

type MQTTSubmitter struct {
	mqtt       *MQTT.Client
	datastream ReadingChannel
	control    ControlChannel
}

//define a function for the default message handler
var f MQTT.MessageHandler = func(client *MQTT.Client, msg MQTT.Message) {
	fmt.Printf("TOPIC: %s\n", msg.Topic())
	fmt.Printf("MSG: %s\n", msg.Payload())
}

func NewMQTTSubmitter(ds ReadingChannel, cc ControlChannel) (*MQTTSubmitter, error) {
	opts := MQTT.NewClientOptions().AddBroker("tcp://localhost:1883")
	opts.SetClientID("SDM630")
	opts.SetDefaultPublishHandler(f)
	c := MQTT.NewClient(opts)
	if token := c.Connect(); token.Wait() && token.Error() != nil {
		return nil, token.Error()
	} else {
		return &MQTTSubmitter{mqtt: c, datastream: ds, control: cc}, nil
	}
}

func (ms *MQTTSubmitter) submitReading(basechannel string,
	subchannel string, reading float32) {
	payload := fmt.Sprintf("%f", reading)
	channel := fmt.Sprintf("%s/%s", basechannel, subchannel)
	token := ms.mqtt.Publish(channel, 0, false, payload)
	token.Wait()
	if token.Error() != nil {
		fmt.Printf("Error: >%s< while submitting %s\r\n", token.Error().Error(), payload)
	}
}

func (ms *MQTTSubmitter) ConsumeData() {
	for {
		// TODO: Read on control, terminate goroutine when
		readings := <-ms.datastream
		basechannel := "SDM630/foo"
		ms.submitReading(basechannel, "L1/Voltage", readings.L1Voltage)
		ms.submitReading(basechannel, "L2/Voltage", readings.L2Voltage)
		ms.submitReading(basechannel, "L3/Voltage", readings.L3Voltage)
		ms.submitReading(basechannel, "L1/Current", readings.L1Current)
		ms.submitReading(basechannel, "L2/Current", readings.L2Current)
		ms.submitReading(basechannel, "L3/Current", readings.L3Current)
		ms.submitReading(basechannel, "L1/Power", readings.L1Power)
		ms.submitReading(basechannel, "L2/Power", readings.L2Power)
		ms.submitReading(basechannel, "L3/Power", readings.L3Power)
		ms.submitReading(basechannel, "L1/CosPhi", readings.L1CosPhi)
		ms.submitReading(basechannel, "L2/CosPhi", readings.L2CosPhi)
		ms.submitReading(basechannel, "L3/CosPhi", readings.L3CosPhi)

	}
	ms.mqtt.Disconnect(250)
}
<commit_msg>added disconnect handler<commit_after>package sdm630

import (
	"fmt"
	MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git"
	"log"
)

type MQTTSubmitter struct {
	mqtt       *MQTT.Client
	datastream ReadingChannel
	control    ControlChannel
}

//define a function for the default message handler
var f MQTT.MessageHandler = func(client *MQTT.Client, msg MQTT.Message) {
	log.Printf("TOPIC: %s - MSG:%s\r\n", msg.Topic(), msg.Payload())
}

//define a function for the connection lost handler
var l MQTT.ConnectionLostHandler = func(client *MQTT.Client, err error) {
	log.Printf("Lost broker connection: %s\r\n", err.Error())
}

func NewMQTTSubmitter(ds ReadingChannel, cc ControlChannel) (*MQTTSubmitter, error) {
	opts := MQTT.NewClientOptions().AddBroker("tcp://localhost:1883")
	opts.SetClientID("SDM630")
	opts.SetDefaultPublishHandler(f)
	opts.SetConnectionLostHandler(l)
	// opts.SetPassword("")
	// opts.SetUsername("")
	opts.SetAutoReconnect(false)
	c := MQTT.NewClient(opts)
	if token := c.Connect(); token.Wait() && token.Error() != nil {
		return nil, token.Error()
	} else {
		return &MQTTSubmitter{mqtt: c, datastream: ds, control: cc}, nil
	}
}

func (ms *MQTTSubmitter) submitReading(basechannel string,
	subchannel string, reading float32) {
	payload := fmt.Sprintf("%f", reading)
	channel := fmt.Sprintf("%s/%s", basechannel, subchannel)
	token := ms.mqtt.Publish(channel, 0, false, payload)
	token.Wait()
	if token.Error() != nil {
		fmt.Printf("Error: >%s< while submitting %s\r\n", token.Error().Error(), payload)
	}
}

func (ms *MQTTSubmitter) ConsumeData() {
	for {
		// TODO: Read on control, terminate goroutine when
		readings := <-ms.datastream
		basechannel := "SDM630/foo"
		ms.submitReading(basechannel, "L1/Voltage", readings.L1Voltage)
		ms.submitReading(basechannel, "L2/Voltage", readings.L2Voltage)
		ms.submitReading(basechannel, "L3/Voltage", readings.L3Voltage)
		ms.submitReading(basechannel, "L1/Current", readings.L1Current)
		ms.submitReading(basechannel, "L2/Current", readings.L2Current)
		ms.submitReading(basechannel, "L3/Current", readings.L3Current)
		ms.submitReading(basechannel, "L1/Power", readings.L1Power)
		ms.submitReading(basechannel, "L2/Power", readings.L2Power)
		ms.submitReading(basechannel, "L3/Power", readings.L3Power)
		ms.submitReading(basechannel, "L1/CosPhi", readings.L1CosPhi)
		ms.submitReading(basechannel, "L2/CosPhi", readings.L2CosPhi)
		ms.submitReading(basechannel, "L3/CosPhi", readings.L3CosPhi)

	}
	ms.mqtt.Disconnect(250)
}
<|endoftext|>