<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"mime/multipart"
	"net/http"
	"net/http/cookiejar"
	"net/url"
	"os"
	"path/filepath"
	"strings"

	"bitbucket.org/apihussain/atmotool/cm"
	"bitbucket.org/apihussain/atmotool/zip"

	"bytes"

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

// Configuration
type Configuration struct {
	Url      string `json:"url"`
	Email    string `json:"email"`
	Password string `json:"password"`
}

type Auth struct {
	Email    string `json:"email"`
	Password string `json:"password"`
}

type Api struct {
	Name    string `json:"name"`
	Version string `json:"version"`
	Id      string `json:"id"`
}

const (
	CUSTOM_LESS_URI = "/resources/theme/default/less?unpack=false"
	LIST_APIS_URI   = "/api/apis"
)

var (
	config Configuration
	client *http.Client
)

func main() {

	usage := `Akana Community Manager Helper Tool.

Usage:
  atmosphere zip --prefix <prefix> [--dir <dir>]
  atmosphere upload less <file> [--config <config>]
  atmosphere upload file --path <path> <files>... [--config <config>]
  atmosphere download --path <path> <filename> [--config <config>]
  atmosphere list apis [--config <config>]
  atmosphere list policies [--config <config>]
  atmosphere rebuild [<theme>] [--config <config>]
  atmosphere -h | --help
  atmosphere --version

Options:
  -h --help  Show help message and exit.
  --version  Show version and exit.
  --dir=<dir>  Directory. [default: .]
  --path=<cms_path>  CM CMS path.
  --config=<config> Configuration file [default: local.conf]
`
	//   atmosphere upload all --config <config> [--dir <dir>]

	arguments, _ := docopt.Parse(usage, nil, true, "1.1.1 cirrus", false)

	// Debug for command-line args
	/*
		var keys []string
		for k := range arguments {
			keys = append(keys, k)
		}
		//sort.Strings(keys)
		// print the argument keys and values
		for _, k := range keys {
			fmt.Printf("%9s %v\n", k, arguments[k])
		}
	*/

	// convert to switch?

	if arguments["upload"] == true {
		configLocation, _ := arguments["--config"].(string)
		err := initializeConfiguration(configLocation)
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}

		if arguments["less"] == true {
			uploadFilePath := arguments["<file>"].(string)
			uploadLessFile(uploadFilePath, config)
		} else if arguments["all"] == true {
			dir, _ := arguments["--dir"].(string)
			uploadAllHelper(dir, config)
		} else if arguments["file"] == true {
			var files []string
			for _, v := range arguments["<files>"].([]string) {
				files = append(files, v)
			}
			path, _ := arguments["--path"].(string)
			upload(files, config, path)
		}
	} else if arguments["zip"] == true {
		prefix, _ := arguments["<prefix>"].(string)
		dir, _ := arguments["--dir"].(string)
		//zip.ZipPredefinedPath(prefix, dir)
		var fn string
		if dir == "." {
			fn = "this"
		} else {
			fn = strings.Replace(dir, ".", "", -1)
			fn = strings.Replace(fn, "/", "-", -1)
		}
		fn = prefix + "_" + fn + ".zip"
		fmt.Printf("Zipping %s as %s...\n", dir, fn)

		zip.ZipFolder(dir, fn)
	} else if arguments["rebuild"] == true {

		configLocation, _ := arguments["--config"].(string)
		err := initializeConfiguration(configLocation)
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}

		theme, _ := arguments["<theme>"].(string)
		if theme == "" {
			theme = "default"
		}

		log.Println("Rebuilding styles for theme:", theme)

		rebuildStyles(theme)
	} else if arguments["list"] == true {
		// List policies
		// List APIs

		configLocation, _ := arguments["--config"].(string)
		err := initializeConfiguration(configLocation)
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}

		if arguments["policies"] == true {
			listPolicies()
		} else if arguments["apis"] == true {
			listApis()
		}
	} else if arguments["download"] == true {
		// Download path as filename.zip
		configLocation, _ := arguments["--config"].(string)
		err := initializeConfiguration(configLocation)
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}

		path, _ := arguments["--path"].(string)
		outputFilename := arguments["<filename>"].(string)
		if !strings.HasSuffix(outputFilename, ".zip") {
			outputFilename += ".zip"
		}
		download(path, outputFilename)
	}
}

func listApis() error {
	//var request *http.Request
	log.Println("Listing APIs")

	err := loginToCM()
	if err != nil {
		log.Fatalln(err)
		return err
	}

	url := config.Url + LIST_APIS_URI

	client := &http.Client{}
	req, err := http.NewRequest("GET", url, nil)
	req.Header.Add("Accept", "application/json")
	resp, err := client.Do(req)

	defer resp.Body.Close()

	bodyBytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}
	var apis cm.ApisResponse
	err = json.Unmarshal(bodyBytes, &apis)
	log.Printf("Found %v APIs", len(apis.Channel.Items))

	var apiList []Api

	for _, v := range apis.Channel.Items {
		//fmt.Printf("%s (%s)\n", v.EntityReference.Title, v.EntityReference.Guid)
		apiList = append(apiList, Api{Name: v.EntityReference.Title})
	}

	jsonBytes, err := json.Marshal(apiList)
	if err != nil {
		log.Printf("Unable to marshall apilist to json")
	}
	fmt.Printf("%s", jsonBytes)

	return nil
}

func listPolicies() error {
	log.Println("Listing Policies")
	return nil
}

// Reads config (or local.conf)
func initializeConfiguration(configLocation string) error {

	if configLocation == "" || configLocation == "<nil>" {
		cwd, err := os.Getwd()
		if err != nil {
			fmt.Println("Cant't get working directory,")
			return err
		}
		configLocation = cwd + "/local.conf"
	}

	configBytes, err := ioutil.ReadFile(configLocation)
	if err != nil {
		fmt.Printf("Error opening config file: %s\n", err)
		return err
	}
	//var config Configuration
	err = json.Unmarshal(configBytes, &config)
	if err != nil {
		fmt.Printf("Unable to parse configuration file: %s\n", err)
		return err
	}

	if len(config.Password) < 1 {
		fmt.Printf("Missing or blank password.")
		return err
	}

	return nil
}

// Convenience method
func uploadLessFile(uploadFilePath string, config Configuration) {
	log.Printf("Uploading Less file %s to %s\n", uploadFilePath, config.Url)

	err := loginToCM()
	if err != nil {
		log.Fatalln(err)
		return
	}

	// Upload
	log.Println("Uploading custom.less ...")
	extraParams := map[string]string{
		"none": "really",
	}
	uploadUri := config.Url + CUSTOM_LESS_URI

	statusCode, err := uploadFile(uploadFilePath, extraParams, uploadUri)
	if err != nil {
		log.Fatalf("Issues. %v : %s", statusCode, err)
	}

	log.Printf("Upload status %v", statusCode)

	if statusCode == 200 {
		err = rebuildStyles("default")
		if err != nil {
			log.Fatalln(err)
		}
	}
}

func uploadFile(uploadFilePath string, extras map[string]string, uploadUri string) (int, error) {
	var uploadStatus int

	var request *http.Request
	request, err := newFileUploadRequest(uploadUri, extras, "File", uploadFilePath)
	if err != nil {
		log.Fatalln(err)
		return uploadStatus, err
	}
	request.Header.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")

	// debug
	/*
		for k, v := range request.Header {
			log.Printf("%s : %s", k, v)
		}
	*/

	resp, err := client.Do(request)
	if err != nil {
		log.Fatal(err)
		return uploadStatus, err
	} else {
		body := &bytes.Buffer{}
		_, err := body.ReadFrom(resp.Body)
		if err != nil {
			log.Fatalln(err)
		}
		resp.Body.Close()

		uploadStatus = resp.StatusCode

		if uploadStatus != 200 {
			b, _ := ioutil.ReadAll(body)
			log.Println(string(b))
		}

		//log.Printf("Upload status %v", resp.StatusCode)
	}
	return uploadStatus, nil
}

func loginToCM() error {
	// Login
	log.Println("Logging in...")
	client = &http.Client{}
	jar, err := cookiejar.New(nil)
	if err != nil {
		log.Fatalln(err)
		return err
	}
	client.Jar = jar

	loginUri := config.Url + "/api/login"
	auth := Auth{config.Email, config.Password}
	buf, err := json.Marshal(auth)
	if err != nil {
		log.Fatalln(err)
		return err
	}
	req, err := http.NewRequest("POST", loginUri, bytes.NewReader(buf))
	req.Header.Add("Accept", "application/json")
	req.Header.Add("Content-Type", "application/json")

	resp, err := client.Do(req)
	if err != nil {
		log.Fatalln(err)
		return err
	}
	if resp.StatusCode != 200 {
		log.Printf("Login %s", resp.Status)
	}

	return nil
}

// Call CM Rebuild Styles
func rebuildStyles(theme string) error {

	// call rebuild styles API
	// POST CM_URI/resources/branding/generatestyles
	// Form Data
	// theme: default
	log.Println("Rebuilding styles...")
	rebuildStylesUri := config.Url + "/resources/branding/generatestyles"
	resp, err := http.PostForm(rebuildStylesUri, url.Values{"theme": {theme}})

	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatalln(err)
		return err
	}
	var results map[string]interface{}
	err = json.Unmarshal(data, &results)
	status := results["result"]
	log.Printf("Rebuild styles: %s", status)
	return nil
}

func newFileUploadRequest(uri string, params map[string]string, paramName string, path string) (*http.Request, error) {

	file, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	body := &bytes.Buffer{}
	writer := multipart.NewWriter(body)

	part, err := writer.CreateFormFile(paramName, filepath.Base(path))
	if err != nil {
		return nil, err
	}

	_, err = io.Copy(part, file)
	if err != nil {
		log.Fatalln(err)
		return nil, err
	}

	/*
		for key, val := range params {
			_ = writer.WriteField(key, val)
		}
	*/

	err = writer.Close()
	if err != nil {
		return nil, err
	}

	req, _ := http.NewRequest("POST", uri, body)
	req.Header.Add("Content-Type", writer.FormDataContentType())

	return req, nil
}

// Convenience method
// TODO
// upload PREFIX_resourcesThemeDefault.zip to CMS /resources/theme/default
// upload PREFIX_contentHomeLanding.zip to CMS /content/home/landing
// upload less file to CMS ??
// call rebuild styles API
func uploadAllHelper(dir string, config Configuration) {
	fmt.Printf("Uploading all in %s to %s\n", dir, config.Url)
}

// Download a CMS path to file
func download(path string, outputFilename string) {
	fmt.Printf("Downloading CMS path %s to file %s\n", path, outputFilename)

	err := loginToCM()
	if err != nil {
		log.Fatalln(err)
		return
	}

	downloadUri := config.Url + path + "?download=true&Zip=true"

	file, err := os.Create(outputFilename)
	if err != nil {
		log.Fatalln(err)
		return
	}
	defer file.Close()

	/*
		check := http.Client{
			CheckRedirect: func(r *http.Request, via []*http.Request) error {
				r.URL.Opaque = r.URL.Path
				return nil
			},
		}
	*/
	resp, err := client.Get(downloadUri)
	if err != nil {
		log.Fatalln(err)
		return
	}
	if resp.StatusCode != 200 {
		log.Fatalln(resp.StatusCode, "Unauthorized access to", downloadUri)
		return
	}
	defer resp.Body.Close()
	log.Println(resp.Status)

	size, err := io.Copy(file, resp.Body)
	if err != nil {
		log.Fatalln(err)
		return
	}
	log.Printf("%s with %v bytes downloaded.", outputFilename, size)
}

// basic upload to CMS
func upload(files []string, config Configuration, path string) {
	fmt.Printf("Uploading to %s cms location %s these: %s\n", config.Url, path, files)
	// upload FILE to CMS path PATH
	// iterate through []FILE

	err := loginToCM()
	if err != nil {
		log.Fatalln(err)
		return
	}

	uploadUri := config.Url + path

	extraParams := map[string]string{
		"none": "really",
	}

	for _, v := range files {
		log.Printf("Uploading %s ...\n", v)
		if strings.HasSuffix(v, ".zip") {
			uploadUri += "?unpack=true"
		}
		//log.Println(uploadUri)
		statusCode, err := uploadFile(v, extraParams, uploadUri)
		if err != nil {
			log.Fatalf("Issues. %v : %s", statusCode, err)
		}
		log.Printf("Upload status %v", statusCode)
	}
}
<commit_msg>fixes #6<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"mime/multipart"
	"net/http"
	"net/http/cookiejar"
	"net/url"
	"os"
	"path/filepath"
	"strings"

	"bitbucket.org/apihussain/atmotool/cm"
	"bitbucket.org/apihussain/atmotool/zip"

	"bytes"

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

// Configuration
type Configuration struct {
	Url      string `json:"url"`
	Email    string `json:"email"`
	Password string `json:"password"`
}

type Auth struct {
	Email    string `json:"email"`
	Password string `json:"password"`
}

type Api struct {
	Name    string `json:"name"`
	Version string `json:"version"`
	Id      string `json:"id"`
}

const (
	CUSTOM_LESS_URI = "/resources/theme/default/less?unpack=false"
	LIST_APIS_URI   = "/api/apis"
)

var (
	config Configuration
	client *http.Client
)

func main() {

	usage := `Akana Community Manager Helper Tool.

Usage:
  atmosphere zip --prefix <prefix> [--dir <dir>]
  atmosphere upload less <file> [--config <config>]
  atmosphere upload file --path <path> <files>... [--config <config>]
  atmosphere download --path <path> <filename> [--config <config>]
  atmosphere list apis [--config <config>]
  atmosphere list policies [--config <config>]
  atmosphere rebuild [<theme>] [--config <config>]
  atmosphere -h | --help
  atmosphere --version

Options:
  -h --help  Show help message and exit.
  --version  Show version and exit.
  --dir=<dir>  Directory. [default: .]
  --path=<cms_path>  CM CMS path.
  --config=<config> Configuration file [default: local.conf]
`
	//   atmosphere upload all --config <config> [--dir <dir>]

	arguments, _ := docopt.Parse(usage, nil, true, "1.1.1 cirrus", false)

	// Debug for command-line args
	/*
		var keys []string
		for k := range arguments {
			keys = append(keys, k)
		}
		//sort.Strings(keys)
		// print the argument keys and values
		for _, k := range keys {
			fmt.Printf("%9s %v\n", k, arguments[k])
		}
	*/

	// convert to switch?

	if arguments["upload"] == true {
		configLocation, _ := arguments["--config"].(string)
		err := initializeConfiguration(configLocation)
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}

		if arguments["less"] == true {
			uploadFilePath := arguments["<file>"].(string)
			uploadLessFile(uploadFilePath, config)
		} else if arguments["all"] == true {
			dir, _ := arguments["--dir"].(string)
			uploadAllHelper(dir, config)
		} else if arguments["file"] == true {
			var files []string
			for _, v := range arguments["<files>"].([]string) {
				files = append(files, v)
			}
			path, _ := arguments["--path"].(string)
			upload(files, config, path)
		}
	} else if arguments["zip"] == true {
		prefix, _ := arguments["<prefix>"].(string)
		dir, _ := arguments["--dir"].(string)
		//zip.ZipPredefinedPath(prefix, dir)
		var fn string
		if dir == "." {
			fn = "this"
		} else {
			fn = strings.Replace(dir, ".", "", -1)
			fn = strings.Replace(fn, "/", "-", -1)
		}
		dir = strings.TrimSuffix(dir, "/")
		fn = prefix + "_" + fn + ".zip"
		fmt.Printf("Zipping %s as %s...\n", dir, fn)

		zip.ZipFolder(dir, fn)
	} else if arguments["rebuild"] == true {

		configLocation, _ := arguments["--config"].(string)
		err := initializeConfiguration(configLocation)
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}

		theme, _ := arguments["<theme>"].(string)
		if theme == "" {
			theme = "default"
		}

		log.Println("Rebuilding styles for theme:", theme)

		rebuildStyles(theme)
	} else if arguments["list"] == true {
		// List policies
		// List APIs

		configLocation, _ := arguments["--config"].(string)
		err := initializeConfiguration(configLocation)
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}

		if arguments["policies"] == true {
			listPolicies()
		} else if arguments["apis"] == true {
			listApis()
		}
	} else if arguments["download"] == true {
		// Download path as filename.zip
		configLocation, _ := arguments["--config"].(string)
		err := initializeConfiguration(configLocation)
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}

		path, _ := arguments["--path"].(string)
		outputFilename := arguments["<filename>"].(string)
		if !strings.HasSuffix(outputFilename, ".zip") {
			outputFilename += ".zip"
		}
		download(path, outputFilename)
	}
}

func listApis() error {
	//var request *http.Request
	log.Println("Listing APIs")

	err := loginToCM()
	if err != nil {
		log.Fatalln(err)
		return err
	}

	url := config.Url + LIST_APIS_URI

	client := &http.Client{}
	req, err := http.NewRequest("GET", url, nil)
	req.Header.Add("Accept", "application/json")
	resp, err := client.Do(req)

	defer resp.Body.Close()

	bodyBytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}
	var apis cm.ApisResponse
	err = json.Unmarshal(bodyBytes, &apis)
	log.Printf("Found %v APIs", len(apis.Channel.Items))

	var apiList []Api

	for _, v := range apis.Channel.Items {
		//fmt.Printf("%s (%s)\n", v.EntityReference.Title, v.EntityReference.Guid)
		apiList = append(apiList, Api{Name: v.EntityReference.Title})
	}

	jsonBytes, err := json.Marshal(apiList)
	if err != nil {
		log.Printf("Unable to marshall apilist to json")
	}
	fmt.Printf("%s", jsonBytes)

	return nil
}

func listPolicies() error {
	log.Println("Listing Policies")
	return nil
}

// Reads config (or local.conf)
func initializeConfiguration(configLocation string) error {

	if configLocation == "" || configLocation == "<nil>" {
		cwd, err := os.Getwd()
		if err != nil {
			fmt.Println("Cant't get working directory,")
			return err
		}
		configLocation = cwd + "/local.conf"
	}

	configBytes, err := ioutil.ReadFile(configLocation)
	if err != nil {
		fmt.Printf("Error opening config file: %s\n", err)
		return err
	}
	//var config Configuration
	err = json.Unmarshal(configBytes, &config)
	if err != nil {
		fmt.Printf("Unable to parse configuration file: %s\n", err)
		return err
	}

	if len(config.Password) < 1 {
		fmt.Printf("Missing or blank password.")
		return err
	}

	return nil
}

// Convenience method
func uploadLessFile(uploadFilePath string, config Configuration) {
	log.Printf("Uploading Less file %s to %s\n", uploadFilePath, config.Url)

	err := loginToCM()
	if err != nil {
		log.Fatalln(err)
		return
	}

	// Upload
	log.Println("Uploading custom.less ...")
	extraParams := map[string]string{
		"none": "really",
	}
	uploadUri := config.Url + CUSTOM_LESS_URI

	statusCode, err := uploadFile(uploadFilePath, extraParams, uploadUri)
	if err != nil {
		log.Fatalf("Issues. %v : %s", statusCode, err)
	}

	log.Printf("Upload status %v", statusCode)

	if statusCode == 200 {
		err = rebuildStyles("default")
		if err != nil {
			log.Fatalln(err)
		}
	}
}

func uploadFile(uploadFilePath string, extras map[string]string, uploadUri string) (int, error) {
	var uploadStatus int

	var request *http.Request
	request, err := newFileUploadRequest(uploadUri, extras, "File", uploadFilePath)
	if err != nil {
		log.Fatalln(err)
		return uploadStatus, err
	}
	request.Header.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")

	// debug
	/*
		for k, v := range request.Header {
			log.Printf("%s : %s", k, v)
		}
	*/

	resp, err := client.Do(request)
	if err != nil {
		log.Fatal(err)
		return uploadStatus, err
	} else {
		body := &bytes.Buffer{}
		_, err := body.ReadFrom(resp.Body)
		if err != nil {
			log.Fatalln(err)
		}
		resp.Body.Close()

		uploadStatus = resp.StatusCode

		if uploadStatus != 200 {
			b, _ := ioutil.ReadAll(body)
			log.Println(string(b))
		}

		//log.Printf("Upload status %v", resp.StatusCode)
	}
	return uploadStatus, nil
}

func loginToCM() error {
	// Login
	log.Println("Logging in...")
	client = &http.Client{}
	jar, err := cookiejar.New(nil)
	if err != nil {
		log.Fatalln(err)
		return err
	}
	client.Jar = jar

	loginUri := config.Url + "/api/login"
	auth := Auth{config.Email, config.Password}
	buf, err := json.Marshal(auth)
	if err != nil {
		log.Fatalln(err)
		return err
	}
	req, err := http.NewRequest("POST", loginUri, bytes.NewReader(buf))
	req.Header.Add("Accept", "application/json")
	req.Header.Add("Content-Type", "application/json")

	resp, err := client.Do(req)
	if err != nil {
		log.Fatalln(err)
		return err
	}
	if resp.StatusCode != 200 {
		log.Printf("Login %s", resp.Status)
	}

	return nil
}

// Call CM Rebuild Styles
func rebuildStyles(theme string) error {

	// call rebuild styles API
	// POST CM_URI/resources/branding/generatestyles
	// Form Data
	// theme: default
	log.Println("Rebuilding styles...")
	rebuildStylesUri := config.Url + "/resources/branding/generatestyles"
	resp, err := http.PostForm(rebuildStylesUri, url.Values{"theme": {theme}})

	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatalln(err)
		return err
	}
	var results map[string]interface{}
	err = json.Unmarshal(data, &results)
	status := results["result"]
	log.Printf("Rebuild styles: %s", status)
	return nil
}

func newFileUploadRequest(uri string, params map[string]string, paramName string, path string) (*http.Request, error) {

	file, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	body := &bytes.Buffer{}
	writer := multipart.NewWriter(body)

	part, err := writer.CreateFormFile(paramName, filepath.Base(path))
	if err != nil {
		return nil, err
	}

	_, err = io.Copy(part, file)
	if err != nil {
		log.Fatalln(err)
		return nil, err
	}

	/*
		for key, val := range params {
			_ = writer.WriteField(key, val)
		}
	*/

	err = writer.Close()
	if err != nil {
		return nil, err
	}

	req, _ := http.NewRequest("POST", uri, body)
	req.Header.Add("Content-Type", writer.FormDataContentType())

	return req, nil
}

// Convenience method
// TODO
// upload PREFIX_resourcesThemeDefault.zip to CMS /resources/theme/default
// upload PREFIX_contentHomeLanding.zip to CMS /content/home/landing
// upload less file to CMS ??
// call rebuild styles API
func uploadAllHelper(dir string, config Configuration) {
	fmt.Printf("Uploading all in %s to %s\n", dir, config.Url)
}

// Download a CMS path to file
func download(path string, outputFilename string) {
	fmt.Printf("Downloading CMS path %s to file %s\n", path, outputFilename)

	err := loginToCM()
	if err != nil {
		log.Fatalln(err)
		return
	}

	downloadUri := config.Url + path + "?download=true&Zip=true"

	file, err := os.Create(outputFilename)
	if err != nil {
		log.Fatalln(err)
		return
	}
	defer file.Close()

	/*
		check := http.Client{
			CheckRedirect: func(r *http.Request, via []*http.Request) error {
				r.URL.Opaque = r.URL.Path
				return nil
			},
		}
	*/
	resp, err := client.Get(downloadUri)
	if err != nil {
		log.Fatalln(err)
		return
	}
	if resp.StatusCode != 200 {
		log.Fatalln(resp.StatusCode, "Unauthorized access to", downloadUri)
		return
	}
	defer resp.Body.Close()
	log.Println(resp.Status)

	size, err := io.Copy(file, resp.Body)
	if err != nil {
		log.Fatalln(err)
		return
	}
	log.Printf("%s with %v bytes downloaded.", outputFilename, size)
}

// basic upload to CMS
func upload(files []string, config Configuration, path string) {
	fmt.Printf("Uploading to %s cms location %s these: %s\n", config.Url, path, files)
	// upload FILE to CMS path PATH
	// iterate through []FILE

	err := loginToCM()
	if err != nil {
		log.Fatalln(err)
		return
	}

	uploadUri := config.Url + path

	extraParams := map[string]string{
		"none": "really",
	}

	for _, v := range files {
		log.Printf("Uploading %s ...\n", v)
		if strings.HasSuffix(v, ".zip") {
			uploadUri += "?unpack=true"
		}
		//log.Println(uploadUri)
		statusCode, err := uploadFile(v, extraParams, uploadUri)
		if err != nil {
			log.Fatalf("Issues. %v : %s", statusCode, err)
		}
		log.Printf("Upload status %v", statusCode)
	}
}
<|endoftext|>


<commit_before>package queries

import (
	"database/sql"
	"fmt"
	"reflect"
	"strings"

	"github.com/pkg/errors"
	"github.com/vattle/sqlboiler/boil"
	"github.com/vattle/sqlboiler/strmangle"
)

type loadRelationshipState struct {
	exec   boil.Executor
	loaded map[string]struct{}
	toLoad []string
}

func (l loadRelationshipState) hasLoaded(depth int) bool {
	_, ok := l.loaded[l.buildKey(depth)]
	return ok
}

func (l loadRelationshipState) setLoaded(depth int) {
	l.loaded[l.buildKey(depth)] = struct{}{}
}

func (l loadRelationshipState) buildKey(depth int) string {
	buf := strmangle.GetBuffer()

	for i, piece := range l.toLoad[:depth+1] {
		if i != 0 {
			buf.WriteByte('.')
		}
		buf.WriteString(piece)
	}

	str := buf.String()
	strmangle.PutBuffer(buf)
	return str
}

// eagerLoad loads all of the model's relationships
//
// toLoad should look like:
// []string{"Relationship", "Relationship.NestedRelationship"} ... etc
func eagerLoad(exec boil.Executor, toLoad []string, obj interface{}, bkind bindKind) error {
	state := loadRelationshipState{
		exec:   exec,
		loaded: map[string]struct{}{},
	}
	for _, toLoad := range toLoad {
		state.toLoad = strings.Split(toLoad, ".")
		if err := state.loadRelationships(0, obj, bkind); err != nil {
			return err
		}
	}

	return nil
}

// loadRelationships dynamically calls the template generated eager load
// functions of the form:
//
//   func (t *TableR) LoadRelationshipName(exec Executor, singular bool, obj interface{})
//
// The arguments to this function are:
//   - t is not considered here, and is always passed nil. The function exists on a loaded
//     struct to avoid a circular dependency with boil, and the receiver is ignored.
//   - exec is used to perform additional queries that might be required for loading the relationships.
//   - singular is passed in to identify whether or not this was a single object
//     or a slice that must be loaded into.
//   - obj is the object or slice of objects, always of the type *obj or *[]*obj as per bind.
func (l loadRelationshipState) loadRelationships(depth int, obj interface{}, bkind bindKind) error {
	typ := reflect.TypeOf(obj).Elem()
	if bkind == kindPtrSliceStruct {
		typ = typ.Elem().Elem()
	}

	loadingFrom := reflect.ValueOf(obj)
	if loadingFrom.IsNil() {
		return nil
	}
	loadingFrom = reflect.Indirect(loadingFrom)

	fmt.Println("load rels", typ.String(), l.toLoad[depth:])

	if !l.hasLoaded(depth) {
		fmt.Println("!loaded", l.toLoad[depth])
		current := l.toLoad[depth]
		ln, found := typ.FieldByName(loaderStructName)
		// It's possible a Loaders struct doesn't exist on the struct.
		if !found {
			return errors.Errorf("attempted to load %s but no L struct was found", current)
		}

		// Attempt to find the LoadRelationshipName function
		loadMethod, found := ln.Type.MethodByName(loadMethodPrefix + current)
		if !found {
			return errors.Errorf("could not find %s%s method for eager loading", loadMethodPrefix, current)
		}

		// Hack to allow nil executors
		execArg := reflect.ValueOf(l.exec)
		if !execArg.IsValid() {
			execArg = reflect.ValueOf((*sql.DB)(nil))
		}

		// Get a loader instance from anything we have, *struct, or *[]*struct
		val := reflect.Indirect(loadingFrom)
		if bkind == kindPtrSliceStruct {
			val = reflect.Indirect(val.Index(0))
		}

		methodArgs := []reflect.Value{
			val.FieldByName(loaderStructName),
			execArg,
			reflect.ValueOf(bkind == kindStruct),
			reflect.ValueOf(obj),
		}
		resp := loadMethod.Func.Call(methodArgs)
		if intf := resp[0].Interface(); intf != nil {
			return errors.Wrapf(intf.(error), "failed to eager load %s", current)
		}

		l.setLoaded(depth)
	} else {
		fmt.Println("!loading", l.toLoad[depth])
	}

	// Check if we can stop
	if depth+1 >= len(l.toLoad) {
		return nil
	}

	// If it's singular we can just immediately call without looping
	if bkind == kindStruct {
		return l.loadRelationshipsRecurse(depth, loadingFrom)
	}

	// Loop over all eager loaded objects
	ln := loadingFrom.Len()
	if ln == 0 {
		return nil
	}
	for i := 0; i < ln; i++ {
		iter := reflect.Indirect(loadingFrom.Index(i))
		if err := l.loadRelationshipsRecurse(depth, iter); err != nil {
			return err
		}
	}

	return nil
}

// loadRelationshipsRecurse is a helper function for taking a reflect.Value and
// Basically calls loadRelationships with: obj.R.EagerLoadedObj, and whether it's a string or slice
func (l loadRelationshipState) loadRelationshipsRecurse(depth int, obj reflect.Value) error {
	// Get relationship struct, and if it's good to go, grab the value we just loaded.
	relationshipStruct := obj.FieldByName(relationshipStructName)
	if !relationshipStruct.IsValid() || relationshipStruct.IsNil() {
		return errors.Errorf("could not traverse into loaded %s relationship to load more things", l.toLoad[depth])
	}

	loadedObject := reflect.Indirect(relationshipStruct).FieldByName(l.toLoad[depth])
	fmt.Println("loadRecurse", l.toLoad[depth])

	// Pop one off the queue
	depth++

	bkind := kindStruct
	if reflect.Indirect(loadedObject).Kind() != reflect.Struct {
		bkind = kindPtrSliceStruct
		loadedObject = loadedObject.Addr()
	}
	return l.loadRelationships(depth, loadedObject.Interface(), bkind)
}
<commit_msg>Use graph descent approach to pass tests<commit_after>package queries

import (
	"database/sql"
	"reflect"
	"strings"

	"github.com/pkg/errors"
	"github.com/vattle/sqlboiler/boil"
	"github.com/vattle/sqlboiler/strmangle"
)

type loadRelationshipState struct {
	exec   boil.Executor
	loaded map[string]struct{}
	toLoad []string
}

func (l loadRelationshipState) hasLoaded(depth int) bool {
	_, ok := l.loaded[l.buildKey(depth)]
	return ok
}

func (l loadRelationshipState) setLoaded(depth int) {
	l.loaded[l.buildKey(depth)] = struct{}{}
}

func (l loadRelationshipState) buildKey(depth int) string {
	buf := strmangle.GetBuffer()

	for i, piece := range l.toLoad[:depth+1] {
		if i != 0 {
			buf.WriteByte('.')
		}
		buf.WriteString(piece)
	}

	str := buf.String()
	strmangle.PutBuffer(buf)
	return str
}

// eagerLoad loads all of the model's relationships
//
// toLoad should look like:
// []string{"Relationship", "Relationship.NestedRelationship"} ... etc
// obj should be one of:
// *[]*struct or *struct
// bkind should reflect what kind of thing it is above
func eagerLoad(exec boil.Executor, toLoad []string, obj interface{}, bkind bindKind) error {
	state := loadRelationshipState{
		exec:   exec,
		loaded: map[string]struct{}{},
	}
	for _, toLoad := range toLoad {
		state.toLoad = strings.Split(toLoad, ".")
		if err := state.loadRelationships(0, obj, bkind); err != nil {
			return err
		}
	}

	return nil
}

// loadRelationships dynamically calls the template generated eager load
// functions of the form:
//
//   func (t *TableR) LoadRelationshipName(exec Executor, singular bool, obj interface{})
//
// The arguments to this function are:
//   - t is not considered here, and is always passed nil. The function exists on a loaded
//     struct to avoid a circular dependency with boil, and the receiver is ignored.
//   - exec is used to perform additional queries that might be required for loading the relationships.
//   - singular is passed in to identify whether or not this was a single object
//     or a slice that must be loaded into.
//   - obj is the object or slice of objects, always of the type *obj or *[]*obj as per bind.
//
// We start with a normal select before eager loading anything: select * from a;
// Then we start eager loading things, it can be represented by a DAG
//          a1, a2           select id, a_id from b where id in (a1, a2)
//         / |    \
//        b1 b2    b3        select id, b_id from c where id in (b2, b3, b4)
//       /   | \     \
//      c1  c2 c3    c4
//
// That's to say that we descend the graph of relationships, and at each level
// we gather all the things up we want to load into, load them, and then move
// to the next level of the graph.
func (l loadRelationshipState) loadRelationships(depth int, obj interface{}, bkind bindKind) error {
	typ := reflect.TypeOf(obj).Elem()
	if bkind == kindPtrSliceStruct {
		typ = typ.Elem().Elem()
	}

	loadingFrom := reflect.ValueOf(obj)
	if loadingFrom.IsNil() {
		return nil
	}

	if !l.hasLoaded(depth) {
		if err := l.callLoadFunction(depth, loadingFrom, typ, bkind); err != nil {
			return err
		}
	}

	// Check if we can stop
	if depth+1 >= len(l.toLoad) {
		return nil
	}

	// *[]*struct -> []*struct
	// *struct -> struct
	loadingFrom = reflect.Indirect(loadingFrom)

	// If it's singular we can just immediately call without looping
	if bkind == kindStruct {
		return l.loadRelationshipsRecurse(depth, loadingFrom)
	}

	// If we were an empty slice to begin with, bail, probably a useless check
	if loadingFrom.Len() == 0 {
		return nil
	}

	// Collect eagerly loaded things to send into next eager load call
	slice, nextBKind, err := collectLoaded(l.toLoad[depth], loadingFrom)
	if err != nil {
		return err
	}

	// If we could collect nothing we're done
	if slice.Len() == 0 {
		return nil
	}

	ptr := reflect.New(slice.Type())
	ptr.Elem().Set(slice)

	return l.loadRelationships(depth+1, ptr.Interface(), nextBKind)
}

// callLoadFunction finds the loader struct, finds the method that we need
// to call and calls it.
func (l loadRelationshipState) callLoadFunction(depth int, loadingFrom reflect.Value, typ reflect.Type, bkind bindKind) error {
	current := l.toLoad[depth]
	ln, found := typ.FieldByName(loaderStructName)
	// It's possible a Loaders struct doesn't exist on the struct.
	if !found {
		return errors.Errorf("attempted to load %s but no L struct was found", current)
	}

	// Attempt to find the LoadRelationshipName function
	loadMethod, found := ln.Type.MethodByName(loadMethodPrefix + current)
	if !found {
		return errors.Errorf("could not find %s%s method for eager loading", loadMethodPrefix, current)
	}

	// Hack to allow nil executors
	execArg := reflect.ValueOf(l.exec)
	if !execArg.IsValid() {
		execArg = reflect.ValueOf((*sql.DB)(nil))
	}

	// Get a loader instance from anything we have, *struct, or *[]*struct
	val := reflect.Indirect(loadingFrom)
	if bkind == kindPtrSliceStruct {
		val = reflect.Indirect(val.Index(0))
	}

	methodArgs := []reflect.Value{
		val.FieldByName(loaderStructName),
		execArg,
		reflect.ValueOf(bkind == kindStruct),
		loadingFrom,
	}

	ret := loadMethod.Func.Call(methodArgs)
	if intf := ret[0].Interface(); intf != nil {
		return errors.Wrapf(intf.(error), "failed to eager load %s", current)
	}

	l.setLoaded(depth)
	return nil
}

// loadRelationshipsRecurse is a helper function for taking a reflect.Value and
// Basically calls loadRelationships with: obj.R.EagerLoadedObj
// Called with an obj of *struct
func (l loadRelationshipState) loadRelationshipsRecurse(depth int, obj reflect.Value) error {
	key := l.toLoad[depth]
	r, err := findRelationshipStruct(obj)
	if err != nil {
		return errors.Wrapf(err, "failed to append loaded %s", key)
	}

	loadedObject := reflect.Indirect(r).FieldByName(key)

	bkind := kindStruct
	if reflect.Indirect(loadedObject).Kind() != reflect.Struct {
		bkind = kindPtrSliceStruct
		loadedObject = loadedObject.Addr()
	}
	return l.loadRelationships(depth+1, loadedObject.Interface(), bkind)
}

// collectLoaded traverses the next level of the graph and picks up all
// the values that we need for the next eager load query.
//
// For example when loadingFrom is [parent1, parent2]
//
//   parent1 -> child1
//          \-> child2
//   parent2 -> child3
//
// This should return [child1, child2, child3]
func collectLoaded(key string, loadingFrom reflect.Value) (reflect.Value, bindKind, error) {
	// Pull the first one so we can get the types out of it in order to
	// create the proper type of slice.
	current := reflect.Indirect(loadingFrom.Index(0))
	lnFrom := loadingFrom.Len()

	r, err := findRelationshipStruct(current)
	if err != nil {
		return reflect.Value{}, 0, errors.Wrapf(err, "failed to collect loaded %s", key)
	}

	loadedObject := reflect.Indirect(r).FieldByName(key)
	loadedType := loadedObject.Type() // Should be *obj or []*obj

	bkind := kindStruct
	if loadedType.Elem().Kind() != reflect.Struct {
		bkind = kindPtrSliceStruct
	}

	collection := reflect.MakeSlice(loadedType, 0, 0)

	i := 0
	for {
		switch bkind {
		case kindStruct:
			collection = reflect.Append(collection, loadedObject)
		case kindPtrSliceStruct:
			collection = reflect.AppendSlice(collection, loadedObject)
		}

		i++
		if i >= lnFrom {
			break
		}

		current = reflect.Indirect(loadingFrom.Index(i))
		r, err = findRelationshipStruct(current)
		if err != nil {
			return reflect.Value{}, 0, errors.Wrapf(err, "failed to collect loaded %s", key)
		}

		loadedObject = reflect.Indirect(r).FieldByName(key)
	}

	return collection, bkind, nil
}

func findRelationshipStruct(obj reflect.Value) (reflect.Value, error) {
	relationshipStruct := obj.FieldByName(relationshipStructName)
	if !relationshipStruct.IsValid() {
		return reflect.Value{}, errors.New("relationship struct was invalid")
	} else if relationshipStruct.IsNil() {
		return reflect.Value{}, errors.New("relationship struct was nil")
	}

	return relationshipStruct, nil
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"flag"
	"html"
	"io"
	"log"
	"os"
	"regexp"
	"strings"
)

var (
	extensionRE = regexp.MustCompile("!
 (.+)
")
	linkRE      = regexp.MustCompile(`\[\[(.+\|)?(.+?)\]\]`)
)

var outputFile *string = flag.String("output", "output.txt", "output file")
var wikiFile *string = flag.String("wikiinput", "download/data", "input Wikipedia data")

func main() {
	flag.Parse()
	lines := make(chan string)
	formats := make(chan fileFormat)
	uniqFormats := make(chan fileFormat)

	go readData(lines)
	go parse(lines, formats)
	go uniq(formats, uniqFormats)
	output(uniqFormats)
}

type fileFormat struct {
	ext string
	use []string
}

func output(formats chan fileFormat) {
	file, err := os.OpenFile(*outputFile, os.O_WRONLY|os.O_CREATE, 0644)
	if err != nil {
		log.Fatal(err)
	}
	defer file.Close()

	for f := range formats {
		out := []string{
			f.ext,           // title (required)
			"A",             // type (required)
			"",              // redirect
			"",              // otheruses (ignore)
			"",              // categories
			"",              // references (ignore)
			"",              // see also
			"",              // further reading (ignore)
			"",              // external links
			"",              // disambiguation (ignore)
			"",              // images
			abstract(f.use), // abstract
			"",              // source url
		}
		_, err = file.WriteString(strings.Join(out, "\t") + "\n")
		if err != nil {
			log.Fatal(err)
		}
	}
}

func abstract(uses []string) string {
	var a string
	// Basic "a" vs "an"; not entirely correct, but close.
	if !strings.HasPrefix(uses[0], "for ") {
		switch uses[0][0] {
		case 'a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U':
			a = "an "
		default:
			a = "a "
		}
	}
	return "A file with this extension may be " + a + strings.Join(uses, ", ") + "."
}

func uniq(formats chan fileFormat, uniqFormats chan fileFormat) {
	uniq := make(map[string]fileFormat)
	for f := range formats {
		if existing, ok := uniq[f.ext]; ok {
			existing.use = append(existing.use, f.use...)
			uniq[f.ext] = existing
		} else {
			uniq[f.ext] = f
		}
	}

	for _, f := range uniq {
		uniqFormats <- f
	}
	close(uniqFormats)
}

func cleanWikiHTML(s string) string {
	return strings.TrimSpace(html.UnescapeString(wikilink(s)))
}

func wikilink(b string) string {
	return linkRE.ReplaceAllString(b, "$2")
}

const (
	parseExtension = iota
	parseDescription
	parseApplication
)

func parse(lines chan string, formats chan fileFormat) {
	state := parseExtension

	// The format we're currently parsing.
	format := fileFormat{}

	for line := range lines {
		switch state {
		case parseExtension:
			// Determine if this line specifies an extension.
			if m := extensionRE.FindStringSubmatch(line); m != nil {
				ext := m[1]

				// Some extensions are used by multiple things. If this is a new extension,
				// send the old one.
				if thisExt := cleanWikiHTML(ext); thisExt != format.ext {
					if format.ext != "" && len(format.use) > 0 {
						formats <- format
					}
					format = fileFormat{ext: thisExt}
				}
				state = parseDescription
			}
		case parseDescription:
			// Collect the possible uses for this extension.
			// This is [1:] to omit a leading "|" on the line.
			if description := line[1:]; len(description) > 0 {
				format.use = append(format.use, cleanWikiHTML(description))
			}
			state = parseApplication
		case parseApplication:
			if app := line[1:]; len(app) > 0 && app != "-" {
				appDescription := "for " + cleanWikiHTML(app)
				// Check if there was a description.
				if last := len(format.use) - 1; last < 0 {
					format.use = append(format.use, appDescription)
				} else {
					format.use[last] += " " + appDescription
				}
			}
			state = parseExtension
		}
	}
	close(formats)
}

func readData(lines chan string) {
	wikiData, err := os.Open(*wikiFile)
	if err != nil {
		log.Fatal(err)
	}
	defer wikiData.Close()

	b := bufio.NewReader(wikiData)

	for {
		line, isPrefix, err := b.ReadLine()
		if err == io.EOF {
			break
		}

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

		if !isPrefix {
			lines <- string(line)
		} else {
			log.Panic("abnormally long input line -- check your data")
		}
	}
	close(lines)
}
<commit_msg>Add redirect from ".pdf" to "pdf" for each extension<commit_after>package main

import (
	"bufio"
	"flag"
	"html"
	"io"
	"log"
	"os"
	"regexp"
	"strings"
)

var (
	extensionRE = regexp.MustCompile("!
 (.+)
")
	linkRE      = regexp.MustCompile(`\[\[(.+\|)?(.+?)\]\]`)
)

var outputFile *string = flag.String("output", "output.txt", "output file")
var wikiFile *string = flag.String("wikiinput", "download/data", "input Wikipedia data")

func main() {
	flag.Parse()
	lines := make(chan string)
	formats := make(chan fileFormat)
	uniqFormats := make(chan fileFormat)

	go readData(lines)
	go parse(lines, formats)
	go uniq(formats, uniqFormats)
	output(uniqFormats)
}

type fileFormat struct {
	ext string
	use []string
}

func output(formats chan fileFormat) {
	file, err := os.OpenFile(*outputFile, os.O_WRONLY|os.O_CREATE, 0644)
	if err != nil {
		log.Fatal(err)
	}
	defer file.Close()

	for f := range formats {
                // Redirect ".pdf" to "pdf"
                redirect := "." + f.ext + "\tR\t" + f.ext + "\t\t\t\t\t\t\t\t\t\t"

		out := []string{
			f.ext,           // title (required)
			"A",             // type (required)
			"",              // redirect
			"",              // otheruses (ignore)
			"",              // categories
			"",              // references (ignore)
			"",              // see also
			"",              // further reading (ignore)
			"",              // external links
			"",              // disambiguation (ignore)
			"",              // images
			abstract(f.use), // abstract
			"",              // source url
		}
		_, err = file.WriteString(strings.Join(out, "\t") + "\n" + redirect + "\n")
		if err != nil {
			log.Fatal(err)
		}
	}
}

func abstract(uses []string) string {
	var a string
	// Basic "a" vs "an"; not entirely correct, but close.
	if !strings.HasPrefix(uses[0], "for ") {
		switch uses[0][0] {
		case 'a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U':
			a = "an "
		default:
			a = "a "
		}
	}
	return "A file with this extension may be " + a + strings.Join(uses, ", ") + "."
}

func uniq(formats chan fileFormat, uniqFormats chan fileFormat) {
	uniq := make(map[string]fileFormat)
	for f := range formats {
		if existing, ok := uniq[f.ext]; ok {
			existing.use = append(existing.use, f.use...)
			uniq[f.ext] = existing
		} else {
			uniq[f.ext] = f
		}
	}

	for _, f := range uniq {
		uniqFormats <- f
	}
	close(uniqFormats)
}

func cleanWikiHTML(s string) string {
	return strings.TrimSpace(html.UnescapeString(wikilink(s)))
}

func wikilink(b string) string {
	return linkRE.ReplaceAllString(b, "$2")
}

const (
	parseExtension = iota
	parseDescription
	parseApplication
)

func parse(lines chan string, formats chan fileFormat) {
	state := parseExtension

	// The format we're currently parsing.
	format := fileFormat{}

	for line := range lines {
		switch state {
		case parseExtension:
			// Determine if this line specifies an extension.
			if m := extensionRE.FindStringSubmatch(line); m != nil {
				ext := m[1]

				// Some extensions are used by multiple things. If this is a new extension,
				// send the old one.
				if thisExt := cleanWikiHTML(ext); thisExt != format.ext {
					if format.ext != "" && len(format.use) > 0 {
						formats <- format
					}
					format = fileFormat{ext: thisExt}
				}
				state = parseDescription
			}
		case parseDescription:
			// Collect the possible uses for this extension.
			// This is [1:] to omit a leading "|" on the line.
			if description := line[1:]; len(description) > 0 {
				format.use = append(format.use, cleanWikiHTML(description))
			}
			state = parseApplication
		case parseApplication:
			if app := line[1:]; len(app) > 0 && app != "-" {
				appDescription := "for " + cleanWikiHTML(app)
				// Check if there was a description.
				if last := len(format.use) - 1; last < 0 {
					format.use = append(format.use, appDescription)
				} else {
					format.use[last] += " " + appDescription
				}
			}
			state = parseExtension
		}
	}
	close(formats)
}

func readData(lines chan string) {
	wikiData, err := os.Open(*wikiFile)
	if err != nil {
		log.Fatal(err)
	}
	defer wikiData.Close()

	b := bufio.NewReader(wikiData)

	for {
		line, isPrefix, err := b.ReadLine()
		if err == io.EOF {
			break
		}

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

		if !isPrefix {
			lines <- string(line)
		} else {
			log.Panic("abnormally long input line -- check your data")
		}
	}
	close(lines)
}
<|endoftext|>


<commit_before>// Status-bar
package mastodon

import (
    "fmt"
    "os"
    "runtime"
    "syscall"
    "time"
)

type Config map[string]string

const (
    STATUS_GOOD = iota
    STATUS_BAD
    STATUS_NORMAL
)

type StatusInfo struct {
    FullText string
    Status int64
}

func NewStatus() *StatusInfo {
    si := new(StatusInfo)
    si.Status = STATUS_NORMAL
    return si
}

func (si *StatusInfo) IsGood() bool {
    return si.Status == STATUS_GOOD
}

func (si *StatusInfo) IsBad() bool {
    return si.Status == STATUS_BAD
}

func Battery(c *Config) *StatusInfo {
    si := NewStatus()
    bi := ReadBatteryInfo(0)
    prefix := "⚠"
    if bi.IsCharging() {
        prefix = "⚡"
    }
    if bi.IsFull() {
        prefix = "ƭ"
        si.FullText = fmt.Sprintf(
            "%s %.1f%%",
            prefix,
            bi.PercentRemaining)
    } else {
        si.FullText = fmt.Sprintf(
            "%s %.1f%% %s (%.1fW)",
            prefix,
            bi.PercentRemaining,
            HumanDuration(int64(bi.SecondsRemaining)),
            bi.Consumption)
    }
    if bi.PercentRemaining < 15 {
        si.Status = STATUS_BAD
    } else if bi.PercentRemaining < 75 {
        si.Status = STATUS_NORMAL
    } else {
        si.Status = STATUS_GOOD
    }
    return si
}

func CPU(c *Config) *StatusInfo {
    si := NewStatus()
    cpuUsage := CpuUsage()
    si.FullText = fmt.Sprintf("Č %.1f%%", cpuUsage)
    if cpuUsage < 15 {
        si.Status = STATUS_GOOD
    } else if cpuUsage < 75 {
        si.Status = STATUS_NORMAL
    } else {
        si.Status = STATUS_BAD
    }
    return si
}

func Disk(c *Config) *StatusInfo {
    si := NewStatus()
    free, total := DiskUsage("/")
    si.FullText = fmt.Sprintf("Đ %s/%s", HumanFileSize(free), HumanFileSize(total))
    if (free / total) < .1 {
        si.Status = STATUS_BAD
    } else {
        si.Status = STATUS_GOOD
    }
    return si
}

func Memory(c *Config) *StatusInfo {
    si := NewStatus()
    free, total := MemInfo()
    used := total - free
    si.FullText = fmt.Sprintf("Ŕ %s/%s", HumanFileSize(used), HumanFileSize(total))
    if (used / total) > .75 {
        si.Status = STATUS_BAD
    } else {
        si.Status = STATUS_GOOD
    }
    return si
}

func LoadAvg(c *Config) *StatusInfo {
    si := NewStatus()
    cpu := float64(runtime.NumCPU())
    one, five, fifteen := ReadLoadAvg()
    si.FullText = fmt.Sprintf("%.2f %.2f %.2f", one, five, fifteen)
    if one > cpu {
        si.Status = STATUS_BAD
    } else {
        si.Status = STATUS_GOOD
    }
    return si
}

func Clock(c *Config) *StatusInfo {
    si := NewStatus()
    si.FullText = time.Now().Format("2006-01-02 15:04:05")
    return si
}

func IPAddress(c *Config) *StatusInfo {
    si := NewStatus()
    si.FullText = IfaceAddr("wlan0")
    return si
}

func Hostname(c *Config) *StatusInfo {
    si := NewStatus()
    host, _ := os.Hostname()
    si.FullText = host
    return si
}

func Uptime(c *Config) *StatusInfo {
    buf := new(syscall.Sysinfo_t)
    syscall.Sysinfo(buf)
    si := NewStatus()
    si.FullText = fmt.Sprintf("U: %s", HumanDuration(buf.Uptime))
    return si
}
<commit_msg>OK, i3bar doesn't like unicode<commit_after>// Status-bar
package mastodon

import (
    "fmt"
    "os"
    "runtime"
    "syscall"
    "time"
)

type Config map[string]string

const (
    STATUS_GOOD = iota
    STATUS_BAD
    STATUS_NORMAL
)

type StatusInfo struct {
    FullText string
    Status int64
}

func NewStatus() *StatusInfo {
    si := new(StatusInfo)
    si.Status = STATUS_NORMAL
    return si
}

func (si *StatusInfo) IsGood() bool {
    return si.Status == STATUS_GOOD
}

func (si *StatusInfo) IsBad() bool {
    return si.Status == STATUS_BAD
}

func Battery(c *Config) *StatusInfo {
    si := NewStatus()
    bi := ReadBatteryInfo(0)
    prefix := "BAT"
    if bi.IsCharging() {
        prefix = "CHR"
    }
    if bi.IsFull() {
        prefix = "FULL"
        si.FullText = fmt.Sprintf(
            "%s %.1f%%",
            prefix,
            bi.PercentRemaining)
    } else {
        si.FullText = fmt.Sprintf(
            "%s %.1f%% %s (%.1fW)",
            prefix,
            bi.PercentRemaining,
            HumanDuration(int64(bi.SecondsRemaining)),
            bi.Consumption)
    }
    if bi.PercentRemaining < 15 {
        si.Status = STATUS_BAD
    } else if bi.PercentRemaining < 75 {
        si.Status = STATUS_NORMAL
    } else {
        si.Status = STATUS_GOOD
    }
    return si
}

func CPU(c *Config) *StatusInfo {
    si := NewStatus()
    cpuUsage := CpuUsage()
    si.FullText = fmt.Sprintf("C %.1f%%", cpuUsage)
    if cpuUsage < 15 {
        si.Status = STATUS_GOOD
    } else if cpuUsage < 75 {
        si.Status = STATUS_NORMAL
    } else {
        si.Status = STATUS_BAD
    }
    return si
}

func Disk(c *Config) *StatusInfo {
    si := NewStatus()
    free, total := DiskUsage("/")
    si.FullText = fmt.Sprintf("D %s/%s", HumanFileSize(free), HumanFileSize(total))
    if (free / total) < .1 {
        si.Status = STATUS_BAD
    } else {
        si.Status = STATUS_GOOD
    }
    return si
}

func Memory(c *Config) *StatusInfo {
    si := NewStatus()
    free, total := MemInfo()
    used := total - free
    si.FullText = fmt.Sprintf("R %s/%s", HumanFileSize(used), HumanFileSize(total))
    if (used / total) > .75 {
        si.Status = STATUS_BAD
    } else {
        si.Status = STATUS_GOOD
    }
    return si
}

func LoadAvg(c *Config) *StatusInfo {
    si := NewStatus()
    cpu := float64(runtime.NumCPU())
    one, five, fifteen := ReadLoadAvg()
    si.FullText = fmt.Sprintf("%.2f %.2f %.2f", one, five, fifteen)
    if one > cpu {
        si.Status = STATUS_BAD
    } else {
        si.Status = STATUS_GOOD
    }
    return si
}

func Clock(c *Config) *StatusInfo {
    si := NewStatus()
    si.FullText = time.Now().Format("2006-01-02 15:04:05")
    return si
}

func IPAddress(c *Config) *StatusInfo {
    si := NewStatus()
    si.FullText = IfaceAddr("wlan0")
    return si
}

func Hostname(c *Config) *StatusInfo {
    si := NewStatus()
    host, _ := os.Hostname()
    si.FullText = host
    return si
}

func Uptime(c *Config) *StatusInfo {
    buf := new(syscall.Sysinfo_t)
    syscall.Sysinfo(buf)
    si := NewStatus()
    si.FullText = fmt.Sprintf("U: %s", HumanDuration(buf.Uptime))
    return si
}
<|endoftext|>


<commit_before>package mastodon

import (
	"bufio"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"path"
	"strings"
	"time"
)

// Config is a setting for access mastodon APIs.
type Config struct {
	Server       string
	ClientID     string
	ClientSecret string
	AccessToken  string
}

// Client is a API client for mastodon.
type Client struct {
	http.Client
	config *Config
}

func (c *Client) doAPI(method string, uri string, params url.Values, res interface{}) error {
	url, err := url.Parse(c.config.Server)
	if err != nil {
		return err
	}
	url.Path = path.Join(url.Path, uri)

	var resp *http.Response
	req, err := http.NewRequest(method, url.String(), strings.NewReader(params.Encode()))
	if err != nil {
		return err
	}
	req.Header.Set("Authorization", "Bearer "+c.config.AccessToken)
	resp, err = c.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if res == nil {
		return nil
	}
	return json.NewDecoder(resp.Body).Decode(&res)
}

// NewClient return new mastodon API client.
func NewClient(config *Config) *Client {
	return &Client{
		Client: *http.DefaultClient,
		config: config,
	}
}

// Authenticate get access-token to the API.
func (c *Client) Authenticate(username, password string) error {
	params := url.Values{}
	params.Set("client_id", c.config.ClientID)
	params.Set("client_secret", c.config.ClientSecret)
	params.Set("grant_type", "password")
	params.Set("username", username)
	params.Set("password", password)
	params.Set("scope", "read write follow")

	url, err := url.Parse(c.config.Server)
	if err != nil {
		return err
	}
	url.Path = path.Join(url.Path, "/oauth/token")

	req, err := http.NewRequest("POST", url.String(), strings.NewReader(params.Encode()))
	if err != nil {
		return err
	}
	resp, err := c.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	res := struct {
		AccessToken string `json:"access_token"`
	}{}
	err = json.NewDecoder(resp.Body).Decode(&res)
	if err != nil {
		return err
	}
	c.config.AccessToken = res.AccessToken
	return nil
}

// AppConfig is a setting for registering applications.
type AppConfig struct {
	http.Client
	Server     string
	ClientName string

	// Where the user should be redirected after authorization (for no redirect, use urn:ietf:wg:oauth:2.0:oob)
	RedirectURIs string

	// This can be a space-separated list of the following items: "read", "write" and "follow".
	Scopes string

	// Optional.
	Website string
}

// Application is mastodon application.
type Application struct {
	ID           int64  `json:"id"`
	RedirectURI  string `json:"redirect_uri"`
	ClientID     string `json:"client_id"`
	ClientSecret string `json:"client_secret"`
}

// RegisterApp returns the mastodon application.
func RegisterApp(appConfig *AppConfig) (*Application, error) {
	params := url.Values{}
	params.Set("client_name", appConfig.ClientName)
	params.Set("redirect_uris", appConfig.RedirectURIs)
	params.Set("scopes", appConfig.Scopes)
	params.Set("website", appConfig.Website)

	url, err := url.Parse(appConfig.Server)
	if err != nil {
		return nil, err
	}
	url.Path = path.Join(url.Path, "/api/v1/apps")

	req, err := http.NewRequest("POST", url.String(), strings.NewReader(params.Encode()))
	if err != nil {
		return nil, err
	}
	resp, err := appConfig.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	app := &Application{}
	err = json.NewDecoder(resp.Body).Decode(app)
	if err != nil {
		return nil, err
	}

	return app, nil
}

// Account hold information for mastodon account.
type Account struct {
	ID             int64     `json:"id"`
	Username       string    `json:"username"`
	Acct           string    `json:"acct"`
	DisplayName    string    `json:"display_name"`
	Locked         bool      `json:"locked"`
	CreatedAt      time.Time `json:"created_at"`
	FollowersCount int64     `json:"followers_count"`
	FollowingCount int64     `json:"following_count"`
	StatusesCount  int64     `json:"statuses_count"`
	Note           string    `json:"note"`
	URL            string    `json:"url"`
	Avatar         string    `json:"avatar"`
	AvatarStatic   string    `json:"avatar_static"`
	Header         string    `json:"header"`
	HeaderStatic   string    `json:"header_static"`
}

// Toot is struct to post status.
type Toot struct {
	Status      string  `json:"status"`
	InReplyToID int64   `json:"in_reply_to_id"`
	MediaIDs    []int64 `json:"media_ids"`
	Sensitive   bool    `json:"sensitive"`
	SpoilerText string  `json:"spoiler_text"`
	Visibility  string  `json:"visibility"`
}

// Status is struct to hold status.
type Status struct {
	ID                 int64         `json:"id"`
	CreatedAt          time.Time     `json:"created_at"`
	InReplyToID        interface{}   `json:"in_reply_to_id"`
	InReplyToAccountID interface{}   `json:"in_reply_to_account_id"`
	Sensitive          bool          `json:"sensitive"`
	SpoilerText        string        `json:"spoiler_text"`
	Visibility         string        `json:"visibility"`
	Application        interface{}   `json:"application"`
	Account            Account       `json:"account"`
	MediaAttachments   []interface{} `json:"media_attachments"`
	Mentions           []interface{} `json:"mentions"`
	Tags               []interface{} `json:"tags"`
	URI                string        `json:"uri"`
	Content            string        `json:"content"`
	URL                string        `json:"url"`
	ReblogsCount       int64         `json:"reblogs_count"`
	FavouritesCount    int64         `json:"favourites_count"`
	Reblog             interface{}   `json:"reblog"`
	Favourited         interface{}   `json:"favourited"`
	Reblogged          interface{}   `json:"reblogged"`
}

// GetAccount return Account.
func (c *Client) GetAccount(id int) (*Account, error) {
	var account Account
	err := c.doAPI("GET", fmt.Sprintf("/api/v1/accounts/%d", id), nil, &account)
	if err != nil {
		return nil, err
	}
	return &account, nil
}

// GetTimelineHome return statuses from home timeline.
func (c *Client) GetTimelineHome() ([]*Status, error) {
	var statuses []*Status
	err := c.doAPI("GET", "/api/v1/timelines/home", nil, &statuses)
	if err != nil {
		return nil, err
	}
	return statuses, nil
}

// PostStatus post the toot.
func (c *Client) PostStatus(toot *Toot) (*Status, error) {
	params := url.Values{}
	params.Set("status", toot.Status)
	if toot.InReplyToID > 0 {
		params.Set("in_reply_to_id", fmt.Sprint(toot.InReplyToID))
	}
	// TODO: media_ids, senstitive, spoiler_text, visibility
	//params.Set("visibility", "public")

	var status Status
	err := c.doAPI("POST", "/api/v1/statuses", params, &status)
	if err != nil {
		return nil, err
	}
	return &status, nil
}

// UpdateEvent is struct for passing status event to app.
type UpdateEvent struct {
	Status *Status
}

func (e *UpdateEvent) event() {}

// NotificationEvent is struct for passing notification event to app.
type NotificationEvent struct {
}

func (e *NotificationEvent) event() {}

// DeleteEvent is struct for passing deletion event to app.
type DeleteEvent struct {
	ID int64
}

func (e *DeleteEvent) event() {}

// ErrorEvent is struct for passing errors to app.
type ErrorEvent struct {
	err error
}

func (e *ErrorEvent) Error() string { return e.err.Error() }
func (e *ErrorEvent) event()        {}

// Event is interface passing events to app.
type Event interface {
	event()
}

// StreamingPublic return channel to read events.
func (c *Client) StreamingPublic(ctx context.Context) (chan Event, error) {
	url, err := url.Parse(c.config.Server)
	if err != nil {
		return nil, err
	}
	url.Path = path.Join(url.Path, "/api/v1/streaming/public")

	var resp *http.Response

	q := make(chan Event, 10)
	go func() {
		defer ctx.Done()

		for {
			req, err := http.NewRequest("GET", url.String(), nil)
			if err == nil {
				req.Header.Set("Authorization", "Bearer "+c.config.AccessToken)
				resp, err = c.Do(req)
			}
			if err == nil {
				name := ""
				s := bufio.NewScanner(io.TeeReader(resp.Body, os.Stdout))
				for s.Scan() {
					line := s.Text()
					token := strings.SplitN(line, ":", 2)
					if len(token) != 2 {
						continue
					}
					switch strings.TrimSpace(token[0]) {
					case "event":
						name = strings.TrimSpace(token[1])
					case "data":
						switch name {
						case "update":
							var status Status
							err = json.Unmarshal([]byte(token[1]), &status)
							if err == nil {
								q <- &UpdateEvent{&status}
							}
						case "notification":
						case "delete":
						}
					default:
					}
				}
				resp.Body.Close()
				err = ctx.Err()
				if err == nil {
					break
				}
			} else {
				q <- &ErrorEvent{err}
			}
			time.Sleep(3 * time.Second)
		}
	}()
	go func() {
		<-ctx.Done()
		if resp != nil && resp.Body != nil {
			resp.Body.Close()
		}
	}()
	return q, nil
}
<commit_msg>add GetAccountFollowers<commit_after>package mastodon

import (
	"bufio"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"path"
	"strings"
	"time"
)

// Config is a setting for access mastodon APIs.
type Config struct {
	Server       string
	ClientID     string
	ClientSecret string
	AccessToken  string
}

// Client is a API client for mastodon.
type Client struct {
	http.Client
	config *Config
}

func (c *Client) doAPI(method string, uri string, params url.Values, res interface{}) error {
	url, err := url.Parse(c.config.Server)
	if err != nil {
		return err
	}
	url.Path = path.Join(url.Path, uri)

	var resp *http.Response
	req, err := http.NewRequest(method, url.String(), strings.NewReader(params.Encode()))
	if err != nil {
		return err
	}
	req.Header.Set("Authorization", "Bearer "+c.config.AccessToken)
	resp, err = c.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if res == nil {
		return nil
	}
	return json.NewDecoder(resp.Body).Decode(&res)
}

// NewClient return new mastodon API client.
func NewClient(config *Config) *Client {
	return &Client{
		Client: *http.DefaultClient,
		config: config,
	}
}

// Authenticate get access-token to the API.
func (c *Client) Authenticate(username, password string) error {
	params := url.Values{}
	params.Set("client_id", c.config.ClientID)
	params.Set("client_secret", c.config.ClientSecret)
	params.Set("grant_type", "password")
	params.Set("username", username)
	params.Set("password", password)
	params.Set("scope", "read write follow")

	url, err := url.Parse(c.config.Server)
	if err != nil {
		return err
	}
	url.Path = path.Join(url.Path, "/oauth/token")

	req, err := http.NewRequest("POST", url.String(), strings.NewReader(params.Encode()))
	if err != nil {
		return err
	}
	resp, err := c.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	res := struct {
		AccessToken string `json:"access_token"`
	}{}
	err = json.NewDecoder(resp.Body).Decode(&res)
	if err != nil {
		return err
	}
	c.config.AccessToken = res.AccessToken
	return nil
}

// AppConfig is a setting for registering applications.
type AppConfig struct {
	http.Client
	Server     string
	ClientName string

	// Where the user should be redirected after authorization (for no redirect, use urn:ietf:wg:oauth:2.0:oob)
	RedirectURIs string

	// This can be a space-separated list of the following items: "read", "write" and "follow".
	Scopes string

	// Optional.
	Website string
}

// Application is mastodon application.
type Application struct {
	ID           int64  `json:"id"`
	RedirectURI  string `json:"redirect_uri"`
	ClientID     string `json:"client_id"`
	ClientSecret string `json:"client_secret"`
}

// RegisterApp returns the mastodon application.
func RegisterApp(appConfig *AppConfig) (*Application, error) {
	params := url.Values{}
	params.Set("client_name", appConfig.ClientName)
	params.Set("redirect_uris", appConfig.RedirectURIs)
	params.Set("scopes", appConfig.Scopes)
	params.Set("website", appConfig.Website)

	url, err := url.Parse(appConfig.Server)
	if err != nil {
		return nil, err
	}
	url.Path = path.Join(url.Path, "/api/v1/apps")

	req, err := http.NewRequest("POST", url.String(), strings.NewReader(params.Encode()))
	if err != nil {
		return nil, err
	}
	resp, err := appConfig.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	app := &Application{}
	err = json.NewDecoder(resp.Body).Decode(app)
	if err != nil {
		return nil, err
	}

	return app, nil
}

// Account hold information for mastodon account.
type Account struct {
	ID             int64     `json:"id"`
	Username       string    `json:"username"`
	Acct           string    `json:"acct"`
	DisplayName    string    `json:"display_name"`
	Locked         bool      `json:"locked"`
	CreatedAt      time.Time `json:"created_at"`
	FollowersCount int64     `json:"followers_count"`
	FollowingCount int64     `json:"following_count"`
	StatusesCount  int64     `json:"statuses_count"`
	Note           string    `json:"note"`
	URL            string    `json:"url"`
	Avatar         string    `json:"avatar"`
	AvatarStatic   string    `json:"avatar_static"`
	Header         string    `json:"header"`
	HeaderStatic   string    `json:"header_static"`
}

// Toot is struct to post status.
type Toot struct {
	Status      string  `json:"status"`
	InReplyToID int64   `json:"in_reply_to_id"`
	MediaIDs    []int64 `json:"media_ids"`
	Sensitive   bool    `json:"sensitive"`
	SpoilerText string  `json:"spoiler_text"`
	Visibility  string  `json:"visibility"`
}

// Status is struct to hold status.
type Status struct {
	ID                 int64         `json:"id"`
	CreatedAt          time.Time     `json:"created_at"`
	InReplyToID        interface{}   `json:"in_reply_to_id"`
	InReplyToAccountID interface{}   `json:"in_reply_to_account_id"`
	Sensitive          bool          `json:"sensitive"`
	SpoilerText        string        `json:"spoiler_text"`
	Visibility         string        `json:"visibility"`
	Application        interface{}   `json:"application"`
	Account            Account       `json:"account"`
	MediaAttachments   []interface{} `json:"media_attachments"`
	Mentions           []interface{} `json:"mentions"`
	Tags               []interface{} `json:"tags"`
	URI                string        `json:"uri"`
	Content            string        `json:"content"`
	URL                string        `json:"url"`
	ReblogsCount       int64         `json:"reblogs_count"`
	FavouritesCount    int64         `json:"favourites_count"`
	Reblog             interface{}   `json:"reblog"`
	Favourited         interface{}   `json:"favourited"`
	Reblogged          interface{}   `json:"reblogged"`
}

// GetAccount return Account.
func (c *Client) GetAccount(id int) (*Account, error) {
	var account Account
	err := c.doAPI("GET", fmt.Sprintf("/api/v1/accounts/%d", id), nil, &account)
	if err != nil {
		return nil, err
	}
	return &account, nil
}

func (c *Client) GetAccountFollowers(id int64) ([]*Account, error) {
	var accounts []*Account
	err := c.doAPI("GET", fmt.Sprintf("/api/v1/accounts/%d/followers", id), nil, &accounts)
	if err != nil {
		return nil, err
	}
	return accounts, nil
}

// GetTimelineHome return statuses from home timeline.
func (c *Client) GetTimelineHome() ([]*Status, error) {
	var statuses []*Status
	err := c.doAPI("GET", "/api/v1/timelines/home", nil, &statuses)
	if err != nil {
		return nil, err
	}
	return statuses, nil
}

// PostStatus post the toot.
func (c *Client) PostStatus(toot *Toot) (*Status, error) {
	params := url.Values{}
	params.Set("status", toot.Status)
	if toot.InReplyToID > 0 {
		params.Set("in_reply_to_id", fmt.Sprint(toot.InReplyToID))
	}
	// TODO: media_ids, senstitive, spoiler_text, visibility
	//params.Set("visibility", "public")

	var status Status
	err := c.doAPI("POST", "/api/v1/statuses", params, &status)
	if err != nil {
		return nil, err
	}
	return &status, nil
}

// UpdateEvent is struct for passing status event to app.
type UpdateEvent struct {
	Status *Status
}

func (e *UpdateEvent) event() {}

// NotificationEvent is struct for passing notification event to app.
type NotificationEvent struct {
}

func (e *NotificationEvent) event() {}

// DeleteEvent is struct for passing deletion event to app.
type DeleteEvent struct {
	ID int64
}

func (e *DeleteEvent) event() {}

// ErrorEvent is struct for passing errors to app.
type ErrorEvent struct {
	err error
}

func (e *ErrorEvent) Error() string { return e.err.Error() }
func (e *ErrorEvent) event()        {}

// Event is interface passing events to app.
type Event interface {
	event()
}

// StreamingPublic return channel to read events.
func (c *Client) StreamingPublic(ctx context.Context) (chan Event, error) {
	url, err := url.Parse(c.config.Server)
	if err != nil {
		return nil, err
	}
	url.Path = path.Join(url.Path, "/api/v1/streaming/public")

	var resp *http.Response

	q := make(chan Event, 10)
	go func() {
		defer ctx.Done()

		for {
			req, err := http.NewRequest("GET", url.String(), nil)
			if err == nil {
				req.Header.Set("Authorization", "Bearer "+c.config.AccessToken)
				resp, err = c.Do(req)
			}
			if err == nil {
				name := ""
				s := bufio.NewScanner(io.TeeReader(resp.Body, os.Stdout))
				for s.Scan() {
					line := s.Text()
					token := strings.SplitN(line, ":", 2)
					if len(token) != 2 {
						continue
					}
					switch strings.TrimSpace(token[0]) {
					case "event":
						name = strings.TrimSpace(token[1])
					case "data":
						switch name {
						case "update":
							var status Status
							err = json.Unmarshal([]byte(token[1]), &status)
							if err == nil {
								q <- &UpdateEvent{&status}
							}
						case "notification":
						case "delete":
						}
					default:
					}
				}
				resp.Body.Close()
				err = ctx.Err()
				if err == nil {
					break
				}
			} else {
				q <- &ErrorEvent{err}
			}
			time.Sleep(3 * time.Second)
		}
	}()
	go func() {
		<-ctx.Done()
		if resp != nil && resp.Body != nil {
			resp.Body.Close()
		}
	}()
	return q, nil
}
<|endoftext|>


<commit_before>package matchers

import (
	"errors"
	"fmt"
	"reflect"
	"testing"
)

type Matcher interface {
	Match(interface{}) bool
	String() string
}

type EqualTo struct {
	V interface{}
}

func (m EqualTo) Match(i interface{}) bool {
	return reflect.DeepEqual(m.V, i)
}

func (m EqualTo) String() string {
	return fmt.Sprintf("equal to %v", m.V)
}

type Is struct {
	V interface{}
}

func (m Is) Match(i interface{}) bool {
	switch m.V.(type) {
	case Matcher:
		return m.V.(Matcher).Match(i)
	}
	return EqualTo{m.V}.Match(i)
}

func (m Is) String() string {
	return fmt.Sprintf("is %v", m.V)
}

type Not struct {
	V interface{}
}

func (m Not) Match(i interface{}) bool {
	return !Is{m.V}.Match(i)
}

func (m Not) String() string {
	return fmt.Sprintf("not %v", m.V)
}

type AllOf []Matcher

func (all AllOf) Match(v interface{}) bool {
	for _, m := range all {
		if !m.Match(v) {
			return false
		}
	}
	return true
}

func (all AllOf) String() string {
	s := ""
	for i, m := range all {
		s += fmt.Sprintf("%v", m)
		if i < len(all)-1 {
			s += ", and "
		}
	}
	return s
}

type AnyOf []Matcher

func (any AnyOf) Match(v interface{}) bool {
	for _, m := range any {
		if m.Match(v) {
			return true
		}
	}
	return false
}

func (any AnyOf) String() string {
	s := ""
	for i, m := range any {
		s += fmt.Sprintf("%v", m)
		if i < len(any)-1 {
			s += ", or "
		}
	}
	return s
}

type Desc struct {
	D string
	M Matcher
}

func (m Desc) Match(i interface{}) bool {
	return m.M.Match(i)
}

func (m Desc) String() string {
	return fmt.Sprintf("%v", m.M)
}

type Fails struct {
}

func (m Fails) Match(i interface{}) bool {
	err := i.(Expect).Confirm()
	return err != nil
}

func (m Fails) String() string {
	return fmt.Sprintf("fails")
}

type Expect struct {
	I interface{}
	M Matcher
}

func (m Expect) String() string {
	return fmt.Sprintf("%v %v", m.I, m.M)
}

func (e Expect) Confirm() error {
	if !e.M.Match(e.I) {
		return errors.New(fmt.Sprintf("%v %v", e.I, e.M))
	}
	return nil
}

func AssertThat(t *testing.T, i interface{}, m Matcher) {
	msg := "expect that"
	if d, ok := m.(Desc); ok {
		msg = d.D
	}
	err := Expect{i, m}.Confirm()
	if err != nil {
		t.Error(fmt.Sprintf("%s: %v", msg, err))
	}
}
<commit_msg>Modify matchers to provude type information<commit_after>package matchers

import (
	"errors"
	"fmt"
	"reflect"
	"strings"
	"testing"
)

type Matcher interface {
	Match(interface{}) bool
	String() string
}

type EqualTo struct {
	V interface{}
}

func (m EqualTo) Match(i interface{}) bool {
	return reflect.DeepEqual(m.V, i)
}

func (m EqualTo) String() string {
	return fmt.Sprintf("equal to %v(%v)", reflect.TypeOf(m.V), m.V)
}

type Is struct {
	V interface{}
}

func (m Is) matcher() Matcher {
	switch m.V.(type) {
	case Matcher:
		return m.V.(Matcher)
	}
	return &EqualTo{m.V}
}

func (m Is) Match(i interface{}) bool {
	return m.matcher().Match(i)
}

func (m Is) String() string {
	return fmt.Sprintf("is %v", m.matcher())
}

type Not struct {
	V interface{}
}

func (m Not) Match(i interface{}) bool {
	return !Is{m.V}.Match(i)
}

func (m Not) String() string {
	return fmt.Sprintf("not %v", m.V)
}

type AllOf []Matcher

func (all AllOf) Match(v interface{}) bool {
	for _, m := range all {
		if !m.Match(v) {
			return false
		}
	}
	return true
}

func (all AllOf) String() string {
	s := []string{}
	for _, m := range all {
		s = append(s, fmt.Sprintf("%v", m))
	}
	return strings.Join(s, ", and ")
}

type AnyOf []Matcher

func (any AnyOf) Match(v interface{}) bool {
	for _, m := range any {
		if m.Match(v) {
			return true
		}
	}
	return false
}

func (any AnyOf) String() string {
	s := ""
	for i, m := range any {
		s += fmt.Sprintf("%v", m)
		if i < len(any)-1 {
			s += ", or "
		}
	}
	return s
}

type Desc struct {
	D string
	M Matcher
}

func (m Desc) Match(i interface{}) bool {
	return m.M.Match(i)
}

func (m Desc) String() string {
	return fmt.Sprintf("%v", m.M)
}

type Fails struct {
}

func (m Fails) Match(i interface{}) bool {
	err := i.(Expect).Confirm()
	return err != nil
}

func (m Fails) String() string {
	return fmt.Sprintf("fails")
}

type Expect struct {
	I interface{}
	M Matcher
}

func (m Expect) String() string {
	return fmt.Sprintf("%v %v", m.I, m.M)
}

func (e Expect) Confirm() error {
	if !e.M.Match(e.I) {
		return errors.New(fmt.Sprintf("%v(%v) %v", reflect.TypeOf(e.I), e.I, e.M))
	}
	return nil
}

func AssertThat(t *testing.T, i interface{}, m Matcher) {
	msg := "expect that"
	if d, ok := m.(Desc); ok {
		msg = d.D
	}
	err := Expect{i, m}.Confirm()
	if err != nil {
		t.Error(fmt.Sprintf("%s: %v", msg, err))
	}
}
<|endoftext|>


<commit_before>package raft

import (
	"log"

	"code.google.com/p/go.net/context"
)

func applyToStore(ents []Entry)   {}
func sendMessages(msgs []Message) {}
func saveStateToDisk(st State)    {}
func saveToDisk(ents []Entry)     {}

func Example_Node() {
	n := Start(context.Background(), 0)

	// stuff to n happens in other goroutines

	// the last known state
	var prev *State
	for {
		// ReadState blocks until there is new state ready.
		st, ents, cents, msgs, err := n.ReadState(context.Background())
		if err != nil {
			log.Fatal(err)
		}

		curr := &st
		if !prev.Equal(st) {
			saveStateToDisk(st)
			prev = curr
		}

		saveToDisk(ents)
		applyToStore(cents)
		sendMessages(msgs)
	}
}
<commit_msg>raft: remove old pointer<commit_after>package raft

import (
	"log"

	"code.google.com/p/go.net/context"
)

func applyToStore(ents []Entry)   {}
func sendMessages(msgs []Message) {}
func saveStateToDisk(st State)    {}
func saveToDisk(ents []Entry)     {}

func Example_Node() {
	n := Start(context.Background(), 0)

	// stuff to n happens in other goroutines

	// the last known state
	var prev State
	for {
		// ReadState blocks until there is new state ready.
		st, ents, cents, msgs, err := n.ReadState(context.Background())
		if err != nil {
			log.Fatal(err)
		}

		if !prev.Equal(st) {
			saveStateToDisk(st)
			prev = st
		}

		saveToDisk(ents)
		go applyToStore(cents)
		sendMessages(msgs)
	}
}
<|endoftext|>


<commit_before>// Copyright 2015 CloudAwan LLC
//
// 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 configuration

import (
	"encoding/json"
	"github.com/cloudawan/cloudone_utility/logger"
	"io"
	"os"
)

var log = logger.GetLog("utility")

const (
	rootPath = "/etc"
	fileName = "configuration.json"
)

type Configuration struct {
	jsonMap         map[string]interface{}
	fileNameAndPath string
}

func CreateConfiguration(programName string, configurationContent string) (*Configuration, error) {
	configuration := new(Configuration)
	if err := configuration.createConfigurationFileIfNotExist(programName, configurationContent); err != nil {
		return nil, err
	}
	if err := configuration.readConfigurationFromFile(); err != nil {
		return nil, err
	}
	return configuration, nil
}

func (configuration *Configuration) GetNative(key string) interface{} {
	return configuration.jsonMap[key]
}

func (configuration *Configuration) GetString(key string) (string, bool) {
	value, ok := configuration.jsonMap[key].(string)
	return value, ok
}

func (configuration *Configuration) GetStringSlice(key string) ([]string, bool) {
	stringSlice := make([]string, 0)
	valueSlice, ok := configuration.jsonMap[key].([]interface{})
	if ok == false {
		return nil, false
	}
	for _, value := range valueSlice {
		text, ok := value.(string)
		if ok == false {
			return nil, false
		}
		stringSlice = append(stringSlice, text)
	}
	return stringSlice, true
}

func (configuration *Configuration) GetInt(key string) (int, bool) {
	number, ok := configuration.jsonMap[key].(float64)
	if ok {
		return int(number), true
	} else {
		return 0, false
	}
}

func (configuration *Configuration) GetFloat64(key string) (float64, bool) {
	value, ok := configuration.jsonMap[key].(float64)
	return value, ok
}

func (configuration *Configuration) createConfigurationFileIfNotExist(programName string, configurationContent string) (returnedError error) {
	defer func() {
		if err := recover(); err != nil {
			log.Error("configuration Error: %s", err)
			log.Error(logger.GetStackTrace(4096, false))
			returnedError = err.(error)
		}
	}()

	directoryPath := rootPath + string(os.PathSeparator) + programName
	if err := os.MkdirAll(directoryPath, os.ModePerm); err != nil {
		return err
	}

	fileNameAndPath := directoryPath + string(os.PathSeparator) + fileName

	if _, err := os.Stat(fileNameAndPath); os.IsNotExist(err) {
		outputFile, err := os.Create(fileNameAndPath)
		if err != nil {
			log.Error("createConfigurationFileIfNotExist create file %s with error: %s", fileNameAndPath, err)
			return err
		}
		outputFile.WriteString(configurationContent)
	}

	configuration.fileNameAndPath = fileNameAndPath

	return nil
}

func (configuration *Configuration) Reload() error {
	return configuration.readConfigurationFromFile()
}

func (configuration *Configuration) readConfigurationFromFile() (returnedError error) {
	defer func() {
		if err := recover(); err != nil {
			log.Error("readConfigurationFromFile Error: %s", err)
			log.Error(logger.GetStackTrace(4096, false))
			returnedError = err.(error)
		}
	}()

	// open input file
	inputFile, err := os.Open(configuration.fileNameAndPath)
	if err != nil {
		log.Error("readConfigurationFromFile open file error: %s", err)
		return err
	}
	defer inputFile.Close()

	byteSlice := make([]byte, 0)
	buffer := make([]byte, 1024)
	for {
		// read a chunk
		n, err := inputFile.Read(buffer)
		if err != nil && err != io.EOF {
			log.Error("readConfigurationFromFile read file error: %s", err)
			return err
		}
		if n == 0 {
			break
		}

		byteSlice = append(byteSlice, buffer[0:n]...)
	}

	jsonMap := make(map[string]interface{})
	err = json.Unmarshal(byteSlice, &jsonMap)
	if err != nil {
		log.Error("readConfigurationFromFile parse file error: %s", err)
		return err
	}

	configuration.jsonMap = jsonMap

	return nil
}
<commit_msg>Add set value<commit_after>// Copyright 2015 CloudAwan LLC
//
// 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 configuration

import (
	"encoding/json"
	"github.com/cloudawan/cloudone_utility/logger"
	"io"
	"os"
)

var log = logger.GetLog("utility")

const (
	rootPath = "/etc"
	fileName = "configuration.json"
)

type Configuration struct {
	jsonMap         map[string]interface{}
	fileNameAndPath string
}

func CreateConfiguration(programName string, configurationContent string) (*Configuration, error) {
	configuration := new(Configuration)
	if err := configuration.createConfigurationFileIfNotExist(programName, configurationContent); err != nil {
		return nil, err
	}
	if err := configuration.readConfigurationFromFile(); err != nil {
		return nil, err
	}
	return configuration, nil
}

func (configuration *Configuration) SetNative(key string, value interface{}) {
	configuration.jsonMap[key] = value
}

func (configuration *Configuration) GetNative(key string) interface{} {
	return configuration.jsonMap[key]
}

func (configuration *Configuration) GetString(key string) (string, bool) {
	value, ok := configuration.jsonMap[key].(string)
	return value, ok
}

func (configuration *Configuration) GetStringSlice(key string) ([]string, bool) {
	stringSlice := make([]string, 0)
	valueSlice, ok := configuration.jsonMap[key].([]interface{})
	if ok == false {
		return nil, false
	}
	for _, value := range valueSlice {
		text, ok := value.(string)
		if ok == false {
			return nil, false
		}
		stringSlice = append(stringSlice, text)
	}
	return stringSlice, true
}

func (configuration *Configuration) GetInt(key string) (int, bool) {
	number, ok := configuration.jsonMap[key].(float64)
	if ok {
		return int(number), true
	} else {
		return 0, false
	}
}

func (configuration *Configuration) GetFloat64(key string) (float64, bool) {
	value, ok := configuration.jsonMap[key].(float64)
	return value, ok
}

func (configuration *Configuration) createConfigurationFileIfNotExist(programName string, configurationContent string) (returnedError error) {
	defer func() {
		if err := recover(); err != nil {
			log.Error("configuration Error: %s", err)
			log.Error(logger.GetStackTrace(4096, false))
			returnedError = err.(error)
		}
	}()

	directoryPath := rootPath + string(os.PathSeparator) + programName
	if err := os.MkdirAll(directoryPath, os.ModePerm); err != nil {
		return err
	}

	fileNameAndPath := directoryPath + string(os.PathSeparator) + fileName

	if _, err := os.Stat(fileNameAndPath); os.IsNotExist(err) {
		outputFile, err := os.Create(fileNameAndPath)
		if err != nil {
			log.Error("createConfigurationFileIfNotExist create file %s with error: %s", fileNameAndPath, err)
			return err
		}
		outputFile.WriteString(configurationContent)
	}

	configuration.fileNameAndPath = fileNameAndPath

	return nil
}

func (configuration *Configuration) Reload() error {
	return configuration.readConfigurationFromFile()
}

func (configuration *Configuration) readConfigurationFromFile() (returnedError error) {
	defer func() {
		if err := recover(); err != nil {
			log.Error("readConfigurationFromFile Error: %s", err)
			log.Error(logger.GetStackTrace(4096, false))
			returnedError = err.(error)
		}
	}()

	// open input file
	inputFile, err := os.Open(configuration.fileNameAndPath)
	if err != nil {
		log.Error("readConfigurationFromFile open file error: %s", err)
		return err
	}
	defer inputFile.Close()

	byteSlice := make([]byte, 0)
	buffer := make([]byte, 1024)
	for {
		// read a chunk
		n, err := inputFile.Read(buffer)
		if err != nil && err != io.EOF {
			log.Error("readConfigurationFromFile read file error: %s", err)
			return err
		}
		if n == 0 {
			break
		}

		byteSlice = append(byteSlice, buffer[0:n]...)
	}

	jsonMap := make(map[string]interface{})
	err = json.Unmarshal(byteSlice, &jsonMap)
	if err != nil {
		log.Error("readConfigurationFromFile parse file error: %s", err)
		return err
	}

	configuration.jsonMap = jsonMap

	return nil
}
<|endoftext|>


<commit_before>/*
Copyright IBM Corp. 2016 All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

		 http://www.apache.org/licenses/LICENSE-2.0

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

package obcpbft

import (
	"fmt"
	"sync"
	"time"

	"github.com/hyperledger/fabric/consensus"
	pb "github.com/hyperledger/fabric/protos"
)

type communicator interface {
	consensus.Communicator
	consensus.Inquirer
}

type broadcaster struct {
	comm communicator

	f        int
	msgChans map[uint64]chan *sendRequest
	closed   sync.WaitGroup
	closedCh chan struct{}
}

type sendRequest struct {
	msg  *pb.Message
	done chan bool
}

func newBroadcaster(self uint64, N int, f int, c communicator) *broadcaster {
	queueSize := 10 // XXX increase after testing

	chans := make(map[uint64]chan *sendRequest)
	b := &broadcaster{
		comm:     c,
		f:        f,
		msgChans: chans,
		closedCh: make(chan struct{}),
	}
	for i := 0; i < N; i++ {
		if uint64(i) == self {
			continue
		}
		chans[uint64(i)] = make(chan *sendRequest, queueSize)
		go b.drainer(uint64(i))
	}
	return b
}

func (b *broadcaster) Close() {
	close(b.closedCh)
	b.closed.Wait()
}

func (b *broadcaster) Wait() {
	b.closed.Wait()
}

func (b *broadcaster) drainerSend(dest uint64, send *sendRequest, printedValidatorNotFound bool) bool {
	defer func() {
		b.closed.Done()
	}()
	h, err := getValidatorHandle(dest)
	if err != nil {
		if !printedValidatorNotFound {
			logger.Warningf("could not get handle for replica %d", dest)
		}
		time.Sleep(time.Second)
		send.done <- false
		return true
	}

	if printedValidatorNotFound {
		logger.Infof("Found handle for replica %d", dest)
		printedValidatorNotFound = false
	}

	err = b.comm.Unicast(send.msg, h)
	if err != nil {
		logger.Warningf("could not send to replica %d: %v", dest, err)
		send.done <- false
	} else {
		send.done <- true
	}

	return false
}

func (b *broadcaster) drainer(dest uint64) {
	printedValidatorNotFound := false

	for {
		select {
		case <-b.closedCh:
			for {
				// Drain the message channel to free calling waiters before we shut down
				select {
				case send := <-b.msgChans[dest]:
					send.done <- false
					b.closed.Done()
				default:
					return
				}
			}
		case send := <-b.msgChans[dest]:
			printedValidatorNotFound = b.drainerSend(dest, send, printedValidatorNotFound)
		}
	}
}

func (b *broadcaster) unicastOne(msg *pb.Message, dest uint64, wait chan bool) {
	select {
	case b.msgChans[dest] <- &sendRequest{
		msg:  msg,
		done: wait,
	}:
	default:
		// If this channel is full, we must discard the message and flag it as done
		wait <- false
		b.closed.Done()
	}
}

func (b *broadcaster) send(msg *pb.Message, dest *uint64) error {
	select {
	case <-b.closedCh:
		return fmt.Errorf("broadcaster closed")
	default:
	}

	var destCount int
	var required int
	if dest != nil {
		destCount = 1
		required = 1
	} else {
		destCount = len(b.msgChans)
		required = destCount - b.f
	}

	wait := make(chan bool, destCount)

	if dest != nil {
		b.closed.Add(1)
		b.unicastOne(msg, *dest, wait)
	} else {
		b.closed.Add(len(b.msgChans))
		for i := range b.msgChans {
			b.unicastOne(msg, i, wait)
		}
	}

	succeeded := 0
	timer := time.NewTimer(time.Second) // TODO, make this configurable

	// This loop will try to send, until one of:
	// a) the required number of sends succeed
	// b) all sends complete regardless of success
	// c) the timeout expires and the required number of sends have returned
outer:
	for i := 0; i < destCount; i++ {
		select {
		case success := <-wait:
			if success {
				succeeded++
				if succeeded >= required {
					break outer
				}
			}
		case <-timer.C:
			for i := i; i < required; i++ {
				<-wait
			}
			break outer
		}
	}

	return nil
}

func (b *broadcaster) Unicast(msg *pb.Message, dest uint64) error {
	return b.send(msg, &dest)
}

func (b *broadcaster) Broadcast(msg *pb.Message) error {
	return b.send(msg, nil)
}
<commit_msg>Incorporate feedback from @corecode<commit_after>/*
Copyright IBM Corp. 2016 All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

		 http://www.apache.org/licenses/LICENSE-2.0

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

package obcpbft

import (
	"fmt"
	"sync"
	"time"

	"github.com/hyperledger/fabric/consensus"
	pb "github.com/hyperledger/fabric/protos"
)

type communicator interface {
	consensus.Communicator
	consensus.Inquirer
}

type broadcaster struct {
	comm communicator

	f        int
	msgChans map[uint64]chan *sendRequest
	closed   sync.WaitGroup
	closedCh chan struct{}
}

type sendRequest struct {
	msg  *pb.Message
	done chan bool
}

func newBroadcaster(self uint64, N int, f int, c communicator) *broadcaster {
	queueSize := 10 // XXX increase after testing

	chans := make(map[uint64]chan *sendRequest)
	b := &broadcaster{
		comm:     c,
		f:        f,
		msgChans: chans,
		closedCh: make(chan struct{}),
	}
	for i := 0; i < N; i++ {
		if uint64(i) == self {
			continue
		}
		chans[uint64(i)] = make(chan *sendRequest, queueSize)
		go b.drainer(uint64(i))
	}
	return b
}

func (b *broadcaster) Close() {
	close(b.closedCh)
	b.closed.Wait()
}

func (b *broadcaster) Wait() {
	b.closed.Wait()
}

func (b *broadcaster) drainerSend(dest uint64, send *sendRequest, successLastTime bool) bool {
	// Note, successLastTime is purely used to avoid flooding the log with unnecessary warning messages when a network problem is encountered
	defer func() {
		b.closed.Done()
	}()
	h, err := getValidatorHandle(dest)
	if err != nil {
		if successLastTime {
			logger.Warningf("could not get handle for replica %d", dest)
		}
		send.done <- false
		return false
	}

	err = b.comm.Unicast(send.msg, h)
	if err != nil {
		if successLastTime {
			logger.Warningf("could not send to replica %d: %v", dest, err)
		}
		send.done <- false
		return false
	}

	send.done <- true
	return true

}

func (b *broadcaster) drainer(dest uint64) {
	successLastTime := false

	for {
		select {
		case send := <-b.msgChans[dest]:
			successLastTime = b.drainerSend(dest, send, successLastTime)
		case <-b.closedCh:
			for {
				// Drain the message channel to free calling waiters before we shut down
				select {
				case send := <-b.msgChans[dest]:
					send.done <- false
					b.closed.Done()
				default:
					return
				}
			}
		}
	}
}

func (b *broadcaster) unicastOne(msg *pb.Message, dest uint64, wait chan bool) {
	select {
	case b.msgChans[dest] <- &sendRequest{
		msg:  msg,
		done: wait,
	}:
	default:
		// If this channel is full, we must discard the message and flag it as done
		wait <- false
		b.closed.Done()
	}
}

func (b *broadcaster) send(msg *pb.Message, dest *uint64) error {
	select {
	case <-b.closedCh:
		return fmt.Errorf("broadcaster closed")
	default:
	}

	var destCount int
	var required int
	if dest != nil {
		destCount = 1
		required = 1
	} else {
		destCount = len(b.msgChans)
		required = destCount - b.f
	}

	wait := make(chan bool, destCount)

	if dest != nil {
		b.closed.Add(1)
		b.unicastOne(msg, *dest, wait)
	} else {
		b.closed.Add(len(b.msgChans))
		for i := range b.msgChans {
			b.unicastOne(msg, i, wait)
		}
	}

	succeeded := 0
	timer := time.NewTimer(time.Second) // TODO, make this configurable

	// This loop will try to send, until one of:
	// a) the required number of sends succeed
	// b) all sends complete regardless of success
	// c) the timeout expires and the required number of sends have returned
outer:
	for i := 0; i < destCount; i++ {
		select {
		case success := <-wait:
			if success {
				succeeded++
				if succeeded >= required {
					break outer
				}
			}
		case <-timer.C:
			for i := i; i < required; i++ {
				<-wait
			}
			break outer
		}
	}

	return nil
}

func (b *broadcaster) Unicast(msg *pb.Message, dest uint64) error {
	return b.send(msg, &dest)
}

func (b *broadcaster) Broadcast(msg *pb.Message) error {
	return b.send(msg, nil)
}
<|endoftext|>


<commit_before>package margelet

import (
	"github.com/zhulik/telegram-bot-api"
)

type Margelet struct {
	bot               TGBotAPI
	MessageResponders []Responder
	CommandResponders map[string]CommandHandler
	running           bool
}

func NewMargelet(token string, verbose bool) (*Margelet, error) {
	bot, err := tgbotapi.NewBotAPI(token)
	if err != nil {
		return nil, err
	}

	bot.Debug = verbose

	return &Margelet{bot, []Responder{}, map[string]CommandHandler{}, true}, nil
}

func NewMargeletFromBot(bot TGBotAPI) (*Margelet, error) {
	return &Margelet{bot, []Responder{}, map[string]CommandHandler{}, true}, nil
}

func (this *Margelet) AddMessageResponder(responder Responder) {
	this.MessageResponders = append(this.MessageResponders, responder)
}

func (this *Margelet) AddCommandResponder(command string, responder Responder) {
	this.CommandResponders[command] = responder
}

func (this *Margelet) Send(c tgbotapi.Chattable) (tgbotapi.Message, error) {
	return this.bot.Send(c)
}

func (this *Margelet) GetFileDirectURL(fileID string) (string, error) {
	return this.bot.GetFileDirectURL(fileID)
}

func (this *Margelet) IsMessageToMe(message tgbotapi.Message) bool {
	return this.bot.IsMessageToMe(message)
}

func (this *Margelet) Run() error {
	updates, err := this.bot.GetUpdatesChan(tgbotapi.UpdateConfig{Timeout: 60})

	if err != nil {
		return err
	}

	for this.running {
		select {
		case update := <-updates:
			message := update.Message
			if message.IsCommand() {
				if responder, ok := this.CommandResponders[message.Command()]; ok {
					this.handleMessage(message, []Responder{responder})
				}
			} else {
				this.handleMessage(message, this.MessageResponders)
			}
		}
	}
	return nil
}

func (this *Margelet) Stop() {
	this.running = false
}

func (this *Margelet) handleMessage(message tgbotapi.Message, responders []Responder) {
	for _, responder := range responders {
		err := responder.Response(this, message)

		if err != nil {
			msg := tgbotapi.NewMessage(message.Chat.ID, "Error occured: "+err.Error())
			this.Send(msg)
		}
	}
}
<commit_msg>Automatically store all chats in redis<commit_after>package margelet

import (
	"github.com/zhulik/telegram-bot-api"
)

type Margelet struct {
	bot               TGBotAPI
	MessageResponders []Responder
	CommandResponders map[string]CommandHandler
	running           bool
}

func NewMargelet(token string, verbose bool) (*Margelet, error) {
	bot, err := tgbotapi.NewBotAPI(token)
	if err != nil {
		return nil, err
	}

	bot.Debug = verbose

	return &Margelet{bot, []Responder{}, map[string]CommandHandler{}, true}, nil
}

func NewMargeletFromBot(bot TGBotAPI) (*Margelet, error) {
	return &Margelet{bot, []Responder{}, map[string]CommandHandler{}, true}, nil
}

func (this *Margelet) AddMessageResponder(responder Responder) {
	this.MessageResponders = append(this.MessageResponders, responder)
}

func (this *Margelet) AddCommandResponder(command string, responder Responder) {
	this.CommandResponders[command] = responder
}

func (this *Margelet) Send(c tgbotapi.Chattable) (tgbotapi.Message, error) {
	return this.bot.Send(c)
}

func (this *Margelet) GetFileDirectURL(fileID string) (string, error) {
	return this.bot.GetFileDirectURL(fileID)
}

func (this *Margelet) IsMessageToMe(message tgbotapi.Message) bool {
	return this.bot.IsMessageToMe(message)
}

func (this *Margelet) Run() error {
	updates, err := this.bot.GetUpdatesChan(tgbotapi.UpdateConfig{Timeout: 60})

	if err != nil {
		return err
	}

	for this.running {
		select {
		case update := <-updates:
			message := update.Message
			Chat.AddChat(message.Chat.ID)
			if message.IsCommand() {
				if responder, ok := this.CommandResponders[message.Command()]; ok {
					this.handleMessage(message, []Responder{responder})
				}
			} else {
				this.handleMessage(message, this.MessageResponders)
			}
		}
	}
	return nil
}

func (this *Margelet) Stop() {
	this.running = false
}

func (this *Margelet) handleMessage(message tgbotapi.Message, responders []Responder) {
	for _, responder := range responders {
		err := responder.Response(this, message)

		if err != nil {
			msg := tgbotapi.NewMessage(message.Chat.ID, "Error occured: "+err.Error())
			this.Send(msg)
		}
	}
}
<|endoftext|>