<commit_before>// BaruwaAPI Golang bindings for Baruwa REST API
// Copyright (C) 2019 Andrew Colin Kissa <andrew@topdog.za.net>

// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.

// Package cmd cmdline client for the Baruwa REST API
package cmd

import (
	"fmt"
	"log"

	"github.com/baruwa-enterprise/baruwa-go/api"
	prettyjson "github.com/hokaccha/go-prettyjson"
	cli "github.com/jawher/mow.cli"
)

func userShow(cmd *cli.Cmd) {
	var (
		b   []byte
		uid *int
		err error
		u   *api.User
		c   *api.Client
	)

	cmd.Spec = "--id"

	uid = cmd.Int(cli.IntOpt{
		Name: "id",
		Desc: "User ID",
	})

	cmd.Action = func() {
		if c, err = GetClient(); err != nil {
			log.Fatal(err)
		}

		if u, err = c.GetUser(*uid); err != nil {
			log.Fatal(err)
		}

		if b, err = prettyjson.Marshal(u); err != nil {
			log.Fatal(err)
		}
		fmt.Printf("%s\n", b)
	}
}

func userCreate(cmd *cli.Cmd) {
	var (
		b                                                                    []byte
		err                                                                  error
		f                                                                    *api.UserForm
		c                                                                    *api.Client
		u                                                                    *api.User
		accountType                                                          *int
		domains, organizations                                               *[]int
		lowScore, highScore                                                  api.LocalFloat64
		enabled, sendReports, spamChecks, blockMacros                        *bool
		username, firstname, lastname, password1, password2, email, timezone *string
	)

	cmd.Spec = "--username --password1 --password2 --email --timezone --domain... [--firstname][--lastname][--account-type][--enable][--send-reports][--spam-checks][--low-score][--high-score][--block-macros][--organization...]"

	username = cmd.String(cli.StringOpt{
		Name: "username",
		Desc: "The username for the account",
	})
	firstname = cmd.String(cli.StringOpt{
		Name: "firstname",
		Desc: "The first names",
	})
	lastname = cmd.String(cli.StringOpt{
		Name: "lastname",
		Desc: "The last names",
	})
	password1 = cmd.String(cli.StringOpt{
		Name: "password1",
		Desc: "The account Password",
	})
	password2 = cmd.String(cli.StringOpt{
		Name: "password2",
		Desc: "Retype account Password",
	})
	email = cmd.String(cli.StringOpt{
		Name: "email",
		Desc: "Email Address",
	})
	timezone = cmd.String(cli.StringOpt{
		Name: "timezone",
		Desc: "Users timezone, all dates and times will be displayed in this timezone",
	})
	accountType = cmd.Int(cli.IntOpt{
		Name:  "account-type",
		Desc:  "The account type",
		Value: 3,
	})
	enabled = cmd.Bool(cli.BoolOpt{
		Name: "enable",
		Desc: "Enable this account",
	})
	sendReports = cmd.Bool(cli.BoolOpt{
		Name: "send-reports",
		Desc: "If enabled the user will receive reports from the system",
	})
	spamChecks = cmd.Bool(cli.BoolOpt{
		Name:  "spam-checks",
		Desc:  "Enable spam checking",
		Value: true,
	})
	// cmd.VarOpt("low-score", lowScore, "The score at which an email is considered to be suspected spam, 0.0 uses system defaults")
	// cmd.VarOpt("high-score", highScore, "The score at which an email is considered to be definitely spam, 0.0 uses system defaults")
	cmd.Var(cli.VarOpt{
		Name:  "low-score",
		Desc:  "The score at which an email is considered to be suspected spam, 0.0 uses system defaults",
		Value: &lowScore,
	})
	cmd.Var(cli.VarOpt{
		Name:  "high-score",
		Desc:  "The score at which an email is considered to be definitely spam, 0.0 uses system defaults",
		Value: &highScore,
	})
	blockMacros = cmd.Bool(cli.BoolOpt{
		Name: "block-macros",
		Desc: "Enable blocking Attachments with Macros",
	})
	domains = cmd.Ints(cli.IntsOpt{
		Name: "domain",
		Desc: "The domains to which this user belongs",
	})
	organizations = cmd.Ints(cli.IntsOpt{
		Name: "organization",
		Desc: "The organizations to which this user belongs",
	})
	cmd.Action = func() {
		f = &api.UserForm{
			Username:      username,
			Firstname:     firstname,
			Lastname:      lastname,
			Password1:     password1,
			Password2:     password2,
			Email:         email,
			Timezone:      timezone,
			AccountType:   accountType,
			Enabled:       enabled,
			SendReport:    sendReports,
			SpamChecks:    spamChecks,
			LowScore:      &lowScore,
			HighScore:     &highScore,
			BlockMacros:   blockMacros,
			Domains:       *domains,
			Organizations: *organizations,
		}

		if c, err = GetClient(); err != nil {
			log.Fatal(err)
		}

		if u, err = c.CreateUser(f); err != nil {
			log.Fatal(err)
		}

		if b, err = prettyjson.Marshal(u); err != nil {
			log.Fatal(err)
		}
		fmt.Printf("%s\n", b)
	}
}

func userUpdate(cmd *cli.Cmd) {
	var (
		uid                                                                                *int
		err                                                                                error
		u                                                                                *api.User
		c                                                                                *api.Client
		domains                                                                                *[]int
		f                                                                                *api.UserForm
		lowScore, highScore                                                                        api.LocalFloat64
		enabled, sendReports, spamChecks, blockMacros                                              *bool
		isSetfirstname, isSetlastname, isSetemail, isSettimezone, isSetEnable, isEnableblockMacros bool
		isSetEnableReports, isEnablespamChecks, isSetdomains, isSetlowScore, isSethighScore        bool
		username, firstname, lastname, email, timezone                                             *string
	)

	cmd.Spec = "--id --username --enable|--disable --enable-reports|--disable-reports --enable-spam-checks|--disable-spam-checks --enable-block-macros|--disable-block-macros [--email][--domain...][--timezone][--firstname][--lastname][--low-score][--high-score]"

	uid = cmd.Int(cli.IntOpt{
		Name: "id",
		Desc: "The user id for the account",
	})
	username = cmd.String(cli.StringOpt{
		Name: "username",
		Desc: "The username for the account",
	})
	firstname = cmd.String(cli.StringOpt{
		Name:      "firstname",
		Desc:      "The first names",
		SetByUser: &isSetfirstname,
	})
	lastname = cmd.String(cli.StringOpt{
		Name:      "lastname",
		Desc:      "The last names",
		SetByUser: &isSetlastname,
	})
	email = cmd.String(cli.StringOpt{
		Name:      "email",
		Desc:      "Email Address",
		SetByUser: &isSetemail,
	})
	timezone = cmd.String(cli.StringOpt{
		Name:      "timezone",
		Desc:      "Users timezone, all dates and times will be displayed in this timezone",
		SetByUser: &isSettimezone,
	})
	enabled = cmd.Bool(cli.BoolOpt{
		Name:      "enable",
		Desc:      "Enable this account",
		SetByUser: &isSetEnable,
	})
	enabled = cmd.Bool(cli.BoolOpt{
		Name: "disable",
		Desc: "Disable this account",
	})
	sendReports = cmd.Bool(cli.BoolOpt{
		Name:      "enable-reports",
		Desc:      "Enable reports for this account",
		SetByUser: &isSetEnableReports,
	})
	sendReports = cmd.Bool(cli.BoolOpt{
		Name: "disable-reports",
		Desc: "Disable reports for this account",
	})
	spamChecks = cmd.Bool(cli.BoolOpt{
		Name:      "enable-spam-checks",
		Desc:      "Enable spam checking",
		SetByUser: &isEnablespamChecks,
	})
	spamChecks = cmd.Bool(cli.BoolOpt{
		Name: "disable-spam-checks",
		Desc: "Disable spam checking",
	})
	cmd.Var(cli.VarOpt{
		Name:      "low-score",
		Desc:      "The score at which an email is considered to be suspected spam, 0.0 uses system defaults",
		Value:     &lowScore,
		SetByUser: &isSetlowScore,
	})
	cmd.Var(cli.VarOpt{
		Name:      "high-score",
		Desc:      "The score at which an email is considered to be definitely spam, 0.0 uses system defaults",
		Value:     &highScore,
		SetByUser: &isSethighScore,
	})
	blockMacros = cmd.Bool(cli.BoolOpt{
		Name:      "enable-block-macros",
		Desc:      "Enable or disable blocking Attachments with Macros",
		SetByUser: &isEnableblockMacros,
	})
	blockMacros = cmd.Bool(cli.BoolOpt{
		Name: "disable-block-macros",
		Desc: "Enable or disable blocking Attachments with Macros",
	})
	domains = cmd.Ints(cli.IntsOpt{
		Name:      "domain",
		Desc:      "The domains to which this user belongs",
		SetByUser: &isSetdomains,
	})
	cmd.Action = func() {
		if c, err = GetClient(); err != nil {
			log.Fatal(err)
		}

		if u, err = c.GetUser(*uid); err != nil {
			log.Fatal(err)
		}

		var doms []int
		for _, dom := range u.Domains {
			doms = append(doms, dom.ID)
		}

		if isSetEnable {
			*enabled = true
		} else {
			*enabled = false
		}
		if isSetEnableReports {
			*sendReports = true
		} else {
			*sendReports = false
		}
		if isEnablespamChecks {
			*spamChecks = true
		} else {
			*spamChecks = false
		}
		if isEnableblockMacros {
			*blockMacros = true
		} else {
			*blockMacros = false
		}

		f = &api.UserForm{
			ID:          uid,
			Username:    &u.Username,
			Firstname:   &u.Firstname,
			Lastname:    &u.Lastname,
			Email:       &u.Email,
			Timezone:    &u.Timezone,
			Enabled:     enabled,
			SendReport:  sendReports,
			SpamChecks:  spamChecks,
			LowScore:    &u.LowScore,
			HighScore:   &u.HighScore,
			BlockMacros: blockMacros,
			Domains:     doms,
		}
		if isSetfirstname {
			f.Firstname = firstname
		}
		if isSetlastname {
			f.Lastname = lastname
		}
		if isSetemail {
			f.Email = email
		}
		if isSettimezone {
			f.Timezone = timezone
		}
		if isSetdomains {
			f.Domains = *domains
		}
		if isSetlowScore {
			f.LowScore = &lowScore
		}
		if isSethighScore {
			f.HighScore = &highScore
		}

		if err = c.UpdateUser(f); err != nil {
			log.Fatal(err)
		}

		fmt.Printf("The user: %s has been updated\n", *username)
	}
}

func userDelete(cmd *cli.Cmd) {
	var uid *int

	uid = cmd.Int(cli.IntOpt{
		Name: "id",
		Desc: "The user id for the account",
	})

	cmd.Spec = "--id"

	cmd.Action = func() {
		var err error
		var c *api.Client

		if c, err = GetClient(); err != nil {
			log.Fatal(err)
		}

		if err = c.DeleteUser(*uid); err != nil {
			log.Fatal(err)
		}

		fmt.Printf("The user with id: %d has been deleted\n", *uid)
	}
}

func usersList(cmd *cli.Cmd) {
	var (
		b   []byte
		err error
		c   *api.Client
		u   *api.UserList
	)

	cmd.Action = func() {
		if c, err = GetClient(); err != nil {
			log.Fatal(err)
		}

		if u, err = c.GetUsers(nil); err != nil {
			log.Fatal(err)
		}

		if len(u.Items) == 0 {
			fmt.Println()
			return
		}

		if b, err = prettyjson.Marshal(u); err != nil {
			log.Fatal(err)
		}
		fmt.Printf("%s\n", b)
	}
}
<commit_msg>PKG: Cleanup commented code<commit_after>// BaruwaAPI Golang bindings for Baruwa REST API
// Copyright (C) 2019 Andrew Colin Kissa <andrew@topdog.za.net>

// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.

// Package cmd cmdline client for the Baruwa REST API
package cmd

import (
	"fmt"
	"log"

	"github.com/baruwa-enterprise/baruwa-go/api"
	prettyjson "github.com/hokaccha/go-prettyjson"
	cli "github.com/jawher/mow.cli"
)

func userShow(cmd *cli.Cmd) {
	var (
		b   []byte
		uid *int
		err error
		u   *api.User
		c   *api.Client
	)

	cmd.Spec = "--id"

	uid = cmd.Int(cli.IntOpt{
		Name: "id",
		Desc: "User ID",
	})

	cmd.Action = func() {
		if c, err = GetClient(); err != nil {
			log.Fatal(err)
		}

		if u, err = c.GetUser(*uid); err != nil {
			log.Fatal(err)
		}

		if b, err = prettyjson.Marshal(u); err != nil {
			log.Fatal(err)
		}
		fmt.Printf("%s\n", b)
	}
}

func userCreate(cmd *cli.Cmd) {
	var (
		b                                                                    []byte
		err                                                                  error
		f                                                                    *api.UserForm
		c                                                                    *api.Client
		u                                                                    *api.User
		accountType                                                          *int
		domains, organizations                                               *[]int
		lowScore, highScore                                                  api.LocalFloat64
		enabled, sendReports, spamChecks, blockMacros                        *bool
		username, firstname, lastname, password1, password2, email, timezone *string
	)

	cmd.Spec = "--username --password1 --password2 --email --timezone --domain... [--firstname][--lastname][--account-type][--enable][--send-reports][--spam-checks][--low-score][--high-score][--block-macros][--organization...]"

	username = cmd.String(cli.StringOpt{
		Name: "username",
		Desc: "The username for the account",
	})
	firstname = cmd.String(cli.StringOpt{
		Name: "firstname",
		Desc: "The first names",
	})
	lastname = cmd.String(cli.StringOpt{
		Name: "lastname",
		Desc: "The last names",
	})
	password1 = cmd.String(cli.StringOpt{
		Name: "password1",
		Desc: "The account Password",
	})
	password2 = cmd.String(cli.StringOpt{
		Name: "password2",
		Desc: "Retype account Password",
	})
	email = cmd.String(cli.StringOpt{
		Name: "email",
		Desc: "Email Address",
	})
	timezone = cmd.String(cli.StringOpt{
		Name: "timezone",
		Desc: "Users timezone, all dates and times will be displayed in this timezone",
	})
	accountType = cmd.Int(cli.IntOpt{
		Name:  "account-type",
		Desc:  "The account type",
		Value: 3,
	})
	enabled = cmd.Bool(cli.BoolOpt{
		Name: "enable",
		Desc: "Enable this account",
	})
	sendReports = cmd.Bool(cli.BoolOpt{
		Name: "send-reports",
		Desc: "If enabled the user will receive reports from the system",
	})
	spamChecks = cmd.Bool(cli.BoolOpt{
		Name:  "spam-checks",
		Desc:  "Enable spam checking",
		Value: true,
	})
	cmd.Var(cli.VarOpt{
		Name:  "low-score",
		Desc:  "The score at which an email is considered to be suspected spam, 0.0 uses system defaults",
		Value: &lowScore,
	})
	cmd.Var(cli.VarOpt{
		Name:  "high-score",
		Desc:  "The score at which an email is considered to be definitely spam, 0.0 uses system defaults",
		Value: &highScore,
	})
	blockMacros = cmd.Bool(cli.BoolOpt{
		Name: "block-macros",
		Desc: "Enable blocking Attachments with Macros",
	})
	domains = cmd.Ints(cli.IntsOpt{
		Name: "domain",
		Desc: "The domains to which this user belongs",
	})
	organizations = cmd.Ints(cli.IntsOpt{
		Name: "organization",
		Desc: "The organizations to which this user belongs",
	})
	cmd.Action = func() {
		f = &api.UserForm{
			Username:      username,
			Firstname:     firstname,
			Lastname:      lastname,
			Password1:     password1,
			Password2:     password2,
			Email:         email,
			Timezone:      timezone,
			AccountType:   accountType,
			Enabled:       enabled,
			SendReport:    sendReports,
			SpamChecks:    spamChecks,
			LowScore:      &lowScore,
			HighScore:     &highScore,
			BlockMacros:   blockMacros,
			Domains:       *domains,
			Organizations: *organizations,
		}

		if c, err = GetClient(); err != nil {
			log.Fatal(err)
		}

		if u, err = c.CreateUser(f); err != nil {
			log.Fatal(err)
		}

		if b, err = prettyjson.Marshal(u); err != nil {
			log.Fatal(err)
		}
		fmt.Printf("%s\n", b)
	}
}

func userUpdate(cmd *cli.Cmd) {
	var (
		uid                                                                                *int
		err                                                                                error
		u                                                                                *api.User
		c                                                                                *api.Client
		domains                                                                                *[]int
		f                                                                                *api.UserForm
		lowScore, highScore                                                                        api.LocalFloat64
		enabled, sendReports, spamChecks, blockMacros                                              *bool
		isSetfirstname, isSetlastname, isSetemail, isSettimezone, isSetEnable, isEnableblockMacros bool
		isSetEnableReports, isEnablespamChecks, isSetdomains, isSetlowScore, isSethighScore        bool
		username, firstname, lastname, email, timezone                                             *string
	)

	cmd.Spec = "--id --username --enable|--disable --enable-reports|--disable-reports --enable-spam-checks|--disable-spam-checks --enable-block-macros|--disable-block-macros [--email][--domain...][--timezone][--firstname][--lastname][--low-score][--high-score]"

	uid = cmd.Int(cli.IntOpt{
		Name: "id",
		Desc: "The user id for the account",
	})
	username = cmd.String(cli.StringOpt{
		Name: "username",
		Desc: "The username for the account",
	})
	firstname = cmd.String(cli.StringOpt{
		Name:      "firstname",
		Desc:      "The first names",
		SetByUser: &isSetfirstname,
	})
	lastname = cmd.String(cli.StringOpt{
		Name:      "lastname",
		Desc:      "The last names",
		SetByUser: &isSetlastname,
	})
	email = cmd.String(cli.StringOpt{
		Name:      "email",
		Desc:      "Email Address",
		SetByUser: &isSetemail,
	})
	timezone = cmd.String(cli.StringOpt{
		Name:      "timezone",
		Desc:      "Users timezone, all dates and times will be displayed in this timezone",
		SetByUser: &isSettimezone,
	})
	enabled = cmd.Bool(cli.BoolOpt{
		Name:      "enable",
		Desc:      "Enable this account",
		SetByUser: &isSetEnable,
	})
	enabled = cmd.Bool(cli.BoolOpt{
		Name: "disable",
		Desc: "Disable this account",
	})
	sendReports = cmd.Bool(cli.BoolOpt{
		Name:      "enable-reports",
		Desc:      "Enable reports for this account",
		SetByUser: &isSetEnableReports,
	})
	sendReports = cmd.Bool(cli.BoolOpt{
		Name: "disable-reports",
		Desc: "Disable reports for this account",
	})
	spamChecks = cmd.Bool(cli.BoolOpt{
		Name:      "enable-spam-checks",
		Desc:      "Enable spam checking",
		SetByUser: &isEnablespamChecks,
	})
	spamChecks = cmd.Bool(cli.BoolOpt{
		Name: "disable-spam-checks",
		Desc: "Disable spam checking",
	})
	cmd.Var(cli.VarOpt{
		Name:      "low-score",
		Desc:      "The score at which an email is considered to be suspected spam, 0.0 uses system defaults",
		Value:     &lowScore,
		SetByUser: &isSetlowScore,
	})
	cmd.Var(cli.VarOpt{
		Name:      "high-score",
		Desc:      "The score at which an email is considered to be definitely spam, 0.0 uses system defaults",
		Value:     &highScore,
		SetByUser: &isSethighScore,
	})
	blockMacros = cmd.Bool(cli.BoolOpt{
		Name:      "enable-block-macros",
		Desc:      "Enable or disable blocking Attachments with Macros",
		SetByUser: &isEnableblockMacros,
	})
	blockMacros = cmd.Bool(cli.BoolOpt{
		Name: "disable-block-macros",
		Desc: "Enable or disable blocking Attachments with Macros",
	})
	domains = cmd.Ints(cli.IntsOpt{
		Name:      "domain",
		Desc:      "The domains to which this user belongs",
		SetByUser: &isSetdomains,
	})
	cmd.Action = func() {
		if c, err = GetClient(); err != nil {
			log.Fatal(err)
		}

		if u, err = c.GetUser(*uid); err != nil {
			log.Fatal(err)
		}

		var doms []int
		for _, dom := range u.Domains {
			doms = append(doms, dom.ID)
		}

		if isSetEnable {
			*enabled = true
		} else {
			*enabled = false
		}
		if isSetEnableReports {
			*sendReports = true
		} else {
			*sendReports = false
		}
		if isEnablespamChecks {
			*spamChecks = true
		} else {
			*spamChecks = false
		}
		if isEnableblockMacros {
			*blockMacros = true
		} else {
			*blockMacros = false
		}

		f = &api.UserForm{
			ID:          uid,
			Username:    &u.Username,
			Firstname:   &u.Firstname,
			Lastname:    &u.Lastname,
			Email:       &u.Email,
			Timezone:    &u.Timezone,
			Enabled:     enabled,
			SendReport:  sendReports,
			SpamChecks:  spamChecks,
			LowScore:    &u.LowScore,
			HighScore:   &u.HighScore,
			BlockMacros: blockMacros,
			Domains:     doms,
		}
		if isSetfirstname {
			f.Firstname = firstname
		}
		if isSetlastname {
			f.Lastname = lastname
		}
		if isSetemail {
			f.Email = email
		}
		if isSettimezone {
			f.Timezone = timezone
		}
		if isSetdomains {
			f.Domains = *domains
		}
		if isSetlowScore {
			f.LowScore = &lowScore
		}
		if isSethighScore {
			f.HighScore = &highScore
		}

		if err = c.UpdateUser(f); err != nil {
			log.Fatal(err)
		}

		fmt.Printf("The user: %s has been updated\n", *username)
	}
}

func userDelete(cmd *cli.Cmd) {
	var uid *int

	uid = cmd.Int(cli.IntOpt{
		Name: "id",
		Desc: "The user id for the account",
	})

	cmd.Spec = "--id"

	cmd.Action = func() {
		var err error
		var c *api.Client

		if c, err = GetClient(); err != nil {
			log.Fatal(err)
		}

		if err = c.DeleteUser(*uid); err != nil {
			log.Fatal(err)
		}

		fmt.Printf("The user with id: %d has been deleted\n", *uid)
	}
}

func usersList(cmd *cli.Cmd) {
	var (
		b   []byte
		err error
		c   *api.Client
		u   *api.UserList
	)

	cmd.Action = func() {
		if c, err = GetClient(); err != nil {
			log.Fatal(err)
		}

		if u, err = c.GetUsers(nil); err != nil {
			log.Fatal(err)
		}

		if len(u.Items) == 0 {
			fmt.Println()
			return
		}

		if b, err = prettyjson.Marshal(u); err != nil {
			log.Fatal(err)
		}
		fmt.Printf("%s\n", b)
	}
}
<|endoftext|>


<commit_before>package s3proxy

import (
	"errors"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/s3"
	"net/http"
	"os"
	"log"
	"strings"
	"time"
)

type S3BucketFileSystem struct {
	Bucket string
	sss *s3.S3
}

func NewS3BucketFileSystem(bucketName string, config *aws.Config) *S3BucketFileSystem {
	return &S3BucketFileSystem{
		Bucket: bucketName,
		sss: s3.New(config),
	}
}

func (self *S3BucketFileSystem) Open(name string) (http.File, error) {
	log.Printf("trying to open %s", name)
	params := s3.GetObjectInput{
		Bucket: aws.String(self.Bucket),
		Key: aws.String(name),
	}
	if !strings.HasSuffix(name, "/")  {
		resp, err := self.sss.GetObject(&params)
		if err == nil {
			return &s3BucketObject{
				Key: name,
				PossibleDir: false,
				response: resp,
			}, nil
		}
	}

	// If we're here, the path MIGHT represent a "directory" in S3
	// To determine if it exists, we must ask S3 for a list of all objects in
	// the bucket that have the prefix of the path.
	listParams := s3.ListObjectsInput{
		Bucket: params.Bucket,
		Delimiter: aws.String("/"),
		Prefix: params.Key,
	}
	resp, err := self.sss.ListObjects(&listParams)
	if err != nil {
		return nil, err
	}
	// If it contains nothing, then the directory doesn't exist
	if len(resp.Contents) <= 0 {
		return nil, os.ErrNotExist
	}

	return &s3BucketDirectory {
		Key: *params.Key,
		response: resp,
	}, nil
}

type s3BucketDirEntry struct {
	obj *s3.Object
}

func (self s3BucketDirEntry) Name() string {
	return *self.obj.Key
}

func (self s3BucketDirEntry) Size() int64 {
	return *self.obj.Size
}

func (self s3BucketDirEntry) Mode() os.FileMode {
	return s3BucketObjectMode
}

func (self s3BucketDirEntry) ModTime() time.Time {
	return *self.obj.LastModified
}

func (self s3BucketDirEntry) IsDir() bool {
	return false
}

func (self s3BucketDirEntry) Sys() interface{} {
	return nil
}

type s3BucketDirectory struct {
	Key string
	response *s3.ListObjectsOutput
}

func (self *s3BucketDirectory) Read(p []byte) (int, error) {
	return 0, nil
}

func (self *s3BucketDirectory) Close() error {
	return nil
}

func (self *s3BucketDirectory) Seek(offset int64, whence int) (int64, error) {
	return 0, errors.New("Unsupported operation: Seek")
}

func (self *s3BucketDirectory) Readdir(count int) ([]os.FileInfo, error) {
	nEntries := len(self.response.Contents)
	entries := make([]os.FileInfo, nEntries, nEntries)
	for i := range self.response.Contents {
		entries[i] = &s3BucketDirEntry{
			obj: self.response.Contents[i],
		}
	}
	return entries, nil
}

func (self *s3BucketDirectory) Stat() (os.FileInfo, error) {
	return self, nil
}

func (self *s3BucketDirectory) Name() string {
	return self.Key
}

func (self *s3BucketDirectory) Size() int64 {
	return 0
}

func (self *s3BucketDirectory) Mode() os.FileMode {
	return os.ModeDir
}

func (self *s3BucketDirectory) ModTime() time.Time {
	return time.Now()
}

func (self *s3BucketDirectory) IsDir() bool {
	return true
}

func (self *s3BucketDirectory) Sys() interface{} {
	return nil
}

const s3BucketObjectMode os.FileMode = 0444

type s3BucketObject struct {
	Key string
	PossibleDir bool
	response *s3.GetObjectOutput
}

func (self *s3BucketObject) Read(p []byte) (int, error) {
	if self.response != nil {
		return self.response.Body.Read(p)
	} else {
		return -1, errors.New("Cannot read a non-existant file or directory!")
	}
}

func (self *s3BucketObject) Seek(offset int64, whence int) (int64, error) {
	return 0, errors.New("Unsupported operation: Seek")
}

func (self *s3BucketObject) Close() error {
	if self.response != nil {
		return self.response.Body.Close()
	}
	return nil
}

func (self *s3BucketObject) Readdir(count int) ([]os.FileInfo, error) {
	return nil, errors.New("Unsupported operation: Readdir")
}

func (self *s3BucketObject) Stat() (os.FileInfo, error) {
	return self, nil
}

func (self *s3BucketObject) Name() string {
	return self.Key
}

func (self *s3BucketObject) Size() int64 {
	if self.response != nil {
		return *self.response.ContentLength
	} else {
		return 0
	}
}

func (self *s3BucketObject) Mode() os.FileMode {
	if self.IsDir() {
		return os.ModeDir
	} else {
		return s3BucketObjectMode
	}
}

func (self *s3BucketObject) ModTime() time.Time {
	if self.response != nil {
		return *self.response.LastModified
	} else {
		return time.Now()
	}
}

func (self *s3BucketObject) IsDir() bool {
	return self.PossibleDir
}

func (self *s3BucketObject) Sys() interface{} {
	return nil
}
<commit_msg>Bugfix introduced in #5<commit_after>package s3proxy

import (
	"errors"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/s3"
	"net/http"
	"os"
	"log"
	"strings"
	"time"
)

type S3BucketFileSystem struct {
	Bucket string
	sss *s3.S3
}

func NewS3BucketFileSystem(bucketName string, config *aws.Config) *S3BucketFileSystem {
	return &S3BucketFileSystem{
		Bucket: bucketName,
		sss: s3.New(config),
	}
}

func (self *S3BucketFileSystem) Open(name string) (http.File, error) {
	log.Printf("trying to open %s", name)
	params := s3.GetObjectInput{
		Bucket: aws.String(self.Bucket),
		Key: aws.String(name),
	}
	if !strings.HasSuffix(name, "/")  {
		resp, err := self.sss.GetObject(&params)
		if err == nil {
			return &s3BucketObject{
				Key: name,
				PossibleDir: false,
				response: resp,
			}, nil
		}
	}

	// If we're here, the path MIGHT represent a "directory" in S3
	// To determine if it exists, we must ask S3 for a list of all objects in
	// the bucket that have the prefix of the path.
	dirKey := strings.TrimLeft(name, "/")
	var maxKeys int64 = 100
	listParams := s3.ListObjectsInput{
		Bucket: params.Bucket,
		Prefix: aws.String(dirKey),
		MaxKeys: &maxKeys,
	}
	resp, err := self.sss.ListObjects(&listParams)
	if err != nil {
		return nil, err
	}
	// If it contains nothing, then the directory doesn't exist
	if len(resp.Contents) <= 0 {
		return nil, os.ErrNotExist
	}

	return &s3BucketDirectory {
		Key: *params.Key,
		response: resp,
	}, nil
}

type s3BucketDirEntry struct {
	obj *s3.Object
}

func (self s3BucketDirEntry) Name() string {
	return *self.obj.Key
}

func (self s3BucketDirEntry) Size() int64 {
	return *self.obj.Size
}

func (self s3BucketDirEntry) Mode() os.FileMode {
	return s3BucketObjectMode
}

func (self s3BucketDirEntry) ModTime() time.Time {
	return *self.obj.LastModified
}

func (self s3BucketDirEntry) IsDir() bool {
	return false
}

func (self s3BucketDirEntry) Sys() interface{} {
	return nil
}

type s3BucketDirectory struct {
	Key string
	response *s3.ListObjectsOutput
}

func (self *s3BucketDirectory) Read(p []byte) (int, error) {
	return 0, nil
}

func (self *s3BucketDirectory) Close() error {
	return nil
}

func (self *s3BucketDirectory) Seek(offset int64, whence int) (int64, error) {
	return 0, errors.New("Unsupported operation: Seek")
}

func (self *s3BucketDirectory) Readdir(count int) ([]os.FileInfo, error) {
	nEntries := len(self.response.Contents)
	entries := make([]os.FileInfo, nEntries, nEntries)
	for i := range self.response.Contents {
		entries[i] = &s3BucketDirEntry{
			obj: self.response.Contents[i],
		}
	}
	return entries, nil
}

func (self *s3BucketDirectory) Stat() (os.FileInfo, error) {
	return self, nil
}

func (self *s3BucketDirectory) Name() string {
	return self.Key
}

func (self *s3BucketDirectory) Size() int64 {
	return 0
}

func (self *s3BucketDirectory) Mode() os.FileMode {
	return os.ModeDir
}

func (self *s3BucketDirectory) ModTime() time.Time {
	return time.Now()
}

func (self *s3BucketDirectory) IsDir() bool {
	return true
}

func (self *s3BucketDirectory) Sys() interface{} {
	return nil
}

const s3BucketObjectMode os.FileMode = 0444

type s3BucketObject struct {
	Key string
	PossibleDir bool
	response *s3.GetObjectOutput
}

func (self *s3BucketObject) Read(p []byte) (int, error) {
	if self.response != nil {
		return self.response.Body.Read(p)
	} else {
		return -1, errors.New("Cannot read a non-existant file or directory!")
	}
}

func (self *s3BucketObject) Seek(offset int64, whence int) (int64, error) {
	return 0, errors.New("Unsupported operation: Seek")
}

func (self *s3BucketObject) Close() error {
	if self.response != nil {
		return self.response.Body.Close()
	}
	return nil
}

func (self *s3BucketObject) Readdir(count int) ([]os.FileInfo, error) {
	return nil, errors.New("Unsupported operation: Readdir")
}

func (self *s3BucketObject) Stat() (os.FileInfo, error) {
	return self, nil
}

func (self *s3BucketObject) Name() string {
	return self.Key
}

func (self *s3BucketObject) Size() int64 {
	if self.response != nil {
		return *self.response.ContentLength
	} else {
		return 0
	}
}

func (self *s3BucketObject) Mode() os.FileMode {
	if self.IsDir() {
		return os.ModeDir
	} else {
		return s3BucketObjectMode
	}
}

func (self *s3BucketObject) ModTime() time.Time {
	if self.response != nil {
		return *self.response.LastModified
	} else {
		return time.Now()
	}
}

func (self *s3BucketObject) IsDir() bool {
	return self.PossibleDir
}

func (self *s3BucketObject) Sys() interface{} {
	return nil
}
<|endoftext|>


<commit_before>// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.

// 单进程高速缓存.
package gcache

import (
    "sync"
    "time"
    "math"
    "sync/atomic"
    "gitee.com/johng/gf/g/os/gtime"
    "gitee.com/johng/gf/g/container/gmap"
    "gitee.com/johng/gf/g/container/gset"
)

// 缓存对象
type Cache struct {
    mu         sync.RWMutex
    data       *gmap.StringInterfaceMap // 存放真实的键值对数据
    eksets     *gmap.IntInterfaceMap    // 存放过期的键名数据
    closed     int32                    // 缓存对象是否关闭
}

// 缓存数据项
type CacheItem struct {
    v interface{} // 缓存键值
    e int64       // 过期时间
}

// 全局缓存管理对象
var cache *Cache = New()

// Cache对象按照缓存键名首字母做了分组
func New() *Cache {
    c := &Cache {
        data    : gmap.NewStringInterfaceMap(),
        eksets  : gmap.NewIntInterfaceMap(),
    }
    c.autoClearLoop()
    return c
}

// (使用全局KV缓存对象)设置kv缓存键值对，过期时间单位为毫秒
func Set(key string, value interface{}, expired int64)  {
    cache.Set(key, value, expired)
}

// (使用全局KV缓存对象)批量设置kv缓存键值对，过期时间单位为毫秒
func BatchSet(data map[string]interface{}, expire int64)  {
    cache.BatchSet(data, expire)
}

// (使用全局KV缓存对象)获取指定键名的值
func Get(key string) interface{} {
    return cache.Get(key)
}

// (使用全局KV缓存对象)删除指定键值对
func Remove(key string) {
    cache.Remove(key)
}

// (使用全局KV缓存对象)批量删除指定键值对
func BatchRemove(keys []string) {
    cache.BatchRemove(keys)
}

// 计算过期缓存的键名
func (c *Cache) getExpireKey(expire int64) int {
    return int(math.Ceil(float64(expire/1000)) + 1)*1000
}

// 获取或者创建一个过期键名存放Set
func (c *Cache) getOrNewExpireSet(ek int) *gset.StringSet {
    c.mu.RLock()
    defer c.mu.RUnlock()
    ekset := c.eksets.Get(ek)
    if ekset == nil {
        s := gset.NewStringSet()
        c.eksets.Set(ek, s)
        return s
    } else {
        return ekset.(*gset.StringSet)
    }
}

// 设置kv缓存键值对，过期时间单位为毫秒，expire<=0表示不过期
func (c *Cache) Set(key string, value interface{}, expire int64) {
    // 查找老的键值过期时间
    olde := int64(0)
    item := c.data.Get(key)
    if item != nil {
        olde = item.(CacheItem).e
    }
    // 保存新的键值对
    newe := int64(math.MaxInt64)
    if expire > 0 {
        newe = gtime.Millisecond() + int64(expire)
    }
    // 删除旧的过期键名
    if olde > 0 && newe != olde {
        c.eksets.Get(c.getExpireKey(olde)).(*gset.StringSet).Remove(key)
    }
    // 保存新的过期键名
    c.getOrNewExpireSet(c.getExpireKey(newe)).Add(key)
    // 最后才真实保存数据
    c.data.Set(key, CacheItem{v: value, e: newe})
}

// 批量设置
func (c *Cache) BatchSet(data map[string]interface{}, expire int64)  {
    l    := make([]string, len(data))
    m    := make(map[string]interface{})
    newe := int64(math.MaxInt64)
    if expire > 0 {
        newe = gtime.Millisecond() + int64(expire)
    }
    for k, v := range m {
        m[k]          = CacheItem{v: v, e: newe}
        l[len(l) - 1] = k
    }
    c.getOrNewExpireSet(c.getExpireKey(newe)).BatchAdd(l)
    c.data.BatchSet(m)
}

// 获取指定键名的值
func (c *Cache) Get(key string) interface{} {
    r := c.data.Get(key)
    if r != nil {
        item := r.(CacheItem)
        if item.e > gtime.Millisecond() {
            return item.v
        }
    }
    return nil
}

// 删除指定键值对
func (c *Cache) Remove(key string) {
    c.data.Remove(key)
}

// 批量删除键值对
func (c *Cache) BatchRemove(keys []string) {
    c.data.BatchRemove(keys)
}

// 获得所有的键名，组成字符串数组返回
func (c *Cache) Keys() []string {
    return c.data.Keys()
}

// 获得所有的值，组成数组返回
func (c *Cache) Values() []interface{} {
    return c.data.Values()
}

// 获得缓存对象的键值对数量
func (c *Cache) Size() int {
    return c.data.Size()
}

// 删除缓存对象
func (c *Cache) Close()  {
    atomic.AddInt32(&c.closed, 1)
}

// 自动清理过期键值对
// 每隔1秒清除过去3秒的键值对数据
func (c *Cache) autoClearLoop() {
    for atomic.LoadInt32(&c.closed) == 0 {
        ek  := c.getExpireKey(gtime.Millisecond())
        eks := []int{ek - 2000, ek - 3000, ek - 4000}
        for _, v := range eks {
            if r := c.eksets.Get(v); r != nil {
                c.data.BatchRemove(r.(*gset.StringSet).Slice())
            }
        }
        c.eksets.BatchRemove(eks)
        time.Sleep(time.Second)
    }
}
<commit_msg>还原gcache<commit_after>// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.

// 单进程缓存.
// @todo 需要新增一个MAP，用于时间与过期键值对的快速处理。
package gcache

import (
    "sync"
    "time"
    "gitee.com/johng/gf/g/os/gtime"
    "gitee.com/johng/gf/g/encoding/ghash"
)

const (
    gDEFAULT_CACHE_GROUP_SIZE = 4 // 默认缓存分区大小，不能超过uint8的最大值
)

// 缓存对象
type Cache struct {
    sync.RWMutex
    g uint8               // 分区大小
    m map[uint8]*CacheMap // 以分区大小数字作为索引
}

// 缓存分区对象
type CacheMap  struct {
    sync.RWMutex
    closed bool              // 对象是否已删除，以便判断停止goroutine
    m map[string]CacheItem   // 键值对
}

// 缓存数据项
type CacheItem struct {
    v interface{} // 缓存键值
    e int64       // 过期时间
}

// 全局缓存管理对象
var cache *Cache = New(gDEFAULT_CACHE_GROUP_SIZE)

// Cache对象按照缓存键名首字母做了分组
func New(group uint8) *Cache {
    c := &Cache {
        g : group,
        m : make(map[uint8]*CacheMap),
    }
    // 初始化分区对象
    var i uint8 = 0
    for ; i < group; i++ {
        m := &CacheMap {
            m : make(map[string]CacheItem),
        }
        c.m[i] = m
        go m.autoClearLoop()
    }
    return c
}

// (使用全局KV缓存对象)设置kv缓存键值对，过期时间单位为毫秒
func Set(k string, v interface{}, expired int64)  {
    cache.Set(k, v, expired)
}

// (使用全局KV缓存对象)批量设置kv缓存键值对，过期时间单位为毫秒
func BatchSet(m map[string]interface{}, expired int64)  {
    cache.BatchSet(m, expired)
}

// (使用全局KV缓存对象)获取指定键名的值
func Get(k string) interface{} {
    return cache.Get(k)
}

// (使用全局KV缓存对象)删除指定键值对
func Remove(k string) {
    cache.Remove(k)
}

// (使用全局KV缓存对象)批量删除指定键值对
func BatchRemove(l []string) {
    cache.BatchRemove(l)
}

// 设置kv缓存键值对，过期时间单位为毫秒
func (c *Cache) Set(k string, v interface{}, expired int64)  {
    c.RLock()
    c.m[c.getIndex(k)].Set(k, v, expired)
    c.RUnlock()
}

// 批量设置
func (c *Cache) BatchSet(m map[string]interface{}, expired int64)  {
    c.RLock()
    for k, v := range m {
        c.m[c.getIndex(k)].Set(k, v, expired)
    }
    c.RUnlock()
}

// 获取指定键名的值
func (c *Cache) Get(k string) interface{} {
    c.RLock()
    r := c.m[c.getIndex(k)].Get(k)
    c.RUnlock()
    return r
}

// 删除指定键值对
func (c *Cache) Remove(k string) {
    c.RLock()
    c.m[c.getIndex(k)].Remove(k)
    c.RUnlock()
}

// 批量删除键值对
func (c *Cache) BatchRemove(l []string) {
    c.RLock()
    for _, k := range l {
        c.m[c.getIndex(k)].Remove(k)
    }
    c.RUnlock()
}

// 获得所有的键名，组成字符串数组返回
func (c *Cache) Keys() []string {
    l := make([]string, 0)
    c.RLock()
    for _, cm := range c.m {
        cm.RLock()
        for k2, _ := range cm.m {
            l = append(l, k2)
        }
        cm.RUnlock()
    }
    c.RUnlock()
    return l
}

// 获得所有的值，组成数组返回
func (c *Cache) Values() []interface{} {
    l := make([]interface{}, 0)
    c.RLock()
    for _, cm := range c.m {
        cm.RLock()
        for _, v2 := range cm.m {
            l = append(l, v2.v)
        }
        cm.RUnlock()
    }
    c.RUnlock()
    return l
}

// 获得缓存对象的键值对数量
func (c *Cache) Size() int {
    var size int
    c.RLock()
    for _, cm := range c.m {
        cm.RLock()
        size += len(cm.m)
        cm.RUnlock()
    }
    c.RUnlock()
    return size
}

// 删除缓存对象
func (c *Cache) Close()  {
    c.RLock()
    for _, cm := range c.m {
        cm.Close()
    }
    c.RUnlock()
}

// 计算缓存的索引
func (c *Cache) getIndex(k string) uint8 {
    return uint8(ghash.BKDRHash([]byte(k)) % uint32(c.g))
}

// 设置kv缓存键值对，过期时间单位为毫秒
func (cm *CacheMap) Set(k string, v interface{}, expired int64)  {
    var e int64
    if expired > 0 {
        e = gtime.Millisecond() + int64(expired)
    }
    cm.Lock()
    cm.m[k] = CacheItem{v: v, e: e}
    cm.Unlock()
}

// 获取指定键名的值
func (cm *CacheMap) Get(k string) interface{} {
    var v interface{}
    cm.RLock()
    if r, ok := cm.m[k]; ok {
        if r.e > 0 && r.e < gtime.Millisecond() {
            v = nil
        } else {
            v = r.v
        }
    }
    cm.RUnlock()
    return v
}

// 删除指定键值对
func (cm *CacheMap) Remove(k string) {
    cm.Lock()
    delete(cm.m, k)
    cm.Unlock()
}

// 关闭缓存分区
func (cm *CacheMap) Close() {
    cm.Lock()
    cm.closed = true
    cm.Unlock()
}

// 是否删除
func (cm *CacheMap) isClosed() bool {
    cm.RLock()
    r := cm.closed
    cm.RUnlock()
    return r
}

// 自动清理过期键值对(每间隔3秒执行)
func (cm *CacheMap) autoClearLoop() {
    for !cm.isClosed() {
        cm.Lock()
        for k, v := range cm.m {
            if v.e > 0 && v.e < gtime.Millisecond() {
                delete(cm.m, k)
            }
        }
        cm.Unlock()
        time.Sleep(3 * time.Second)
    }
}

<|endoftext|>


<commit_before>package mackerel

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

/*
{
  "monitors": [
    {
      "id": "2cSZzK3XfmG",
      "type": "connectivity",
      "isMute": false,
      "scopes": [],
      "excludeScopes": []
    },
    {
      "id"  : "2cSZzK3XfmG",
      "type": "host",
      "isMute": false,
      "name": "disk.aa-00.writes.delta",
      "duration": 3,
      "metric": "disk.aa-00.writes.delta",
      "operator": ">",
      "warning": 20000.0,
      "critical": 400000.0,
      "scopes": [
        "SomeService"
      ],
      "excludeScopes": [
        "SomeService: db-slave-backup"
      ],
      "maxCheckAttempts": 1,
      "notificationInterval": 60
    },
    {
      "id"  : "2cSZzK3XfmG",
      "type": "service",
      "isMute": false,
      "name": "SomeService - custom.access_num.4xx_count",
      "service": "SomeService",
      "duration": 1,
      "metric": "custom.access_num.4xx_count",
      "operator": ">",
      "warning": 50.0,
      "critical": 100.0,
      "maxCheckAttempts": 1
    },
    {
      "id"  : "2cSZzK3XfmG",
      "type": "external",
      "isMute": false,
      "name": "example.com",
      "method": "GET",
      "url": "http://www.example.com",
      "service": "SomeService",
      "maxCheckAttempts": 1,
      "responseTimeCritical": 10000,
      "responseTimeWarning": 5000,
      "responseTimeDuration": 5,
      "certificationExpirationCritical": 15,
      "certificationExpirationWarning": 30,
      "requestBody": "Request Body",
      "containsString": "Example",
      "skipCertificateVerification": true,
      "headers": [
        { "name": "Cache-Control", "value": "no-cache"}
      ]
    }
  ]
}
*/

// Monitor represents interface to which each monitor type must confirm to.
type Monitor interface {
	MonitorType() string
	MonitorID() string
	MonitorName() string

	isMonitor()
}

const (
	monitorTypeConnectivity  = "connectivity"
	monitorTypeHostMeric     = "host"
	monitorTypeServiceMetric = "service"
	monitorTypeExternalHTTP  = "external"
	monitorTypeExpression    = "expression"
)

// Ensure each monitor type conforms to the Monitor interface.
var (
	_ Monitor = (*MonitorConnectivity)(nil)
	_ Monitor = (*MonitorHostMetric)(nil)
	_ Monitor = (*MonitorServiceMetric)(nil)
	_ Monitor = (*MonitorExternalHTTP)(nil)
	_ Monitor = (*MonitorExpression)(nil)
)

// Ensure only monitor types defined in this package can be assigned to the
// Monitor interface.
//
func (m *MonitorConnectivity) isMonitor()  {}
func (m *MonitorHostMetric) isMonitor()    {}
func (m *MonitorServiceMetric) isMonitor() {}
func (m *MonitorExternalHTTP) isMonitor()  {}
func (m *MonitorExpression) isMonitor()    {}

// MonitorConnectivity represents connectivity monitor.
type MonitorConnectivity struct {
	ID                   string `json:"id,omitempty"`
	Name                 string `json:"name,omitempty"`
	Memo                 string `json:"memo,omitempty"`
	Type                 string `json:"type,omitempty"`
	IsMute               bool   `json:"isMute,omitempty"`
	NotificationInterval uint64 `json:"notificationInterval,omitempty"`

	Scopes        []string `json:"scopes,omitempty"`
	ExcludeScopes []string `json:"excludeScopes,omitempty"`
}

// MonitorType returns monitor type.
func (m *MonitorConnectivity) MonitorType() string { return monitorTypeConnectivity }

// MonitorName returns monitor name.
func (m *MonitorConnectivity) MonitorName() string { return m.Name }

// MonitorID returns monitor id.
func (m *MonitorConnectivity) MonitorID() string { return m.ID }

// MonitorHostMetric represents host metric monitor.
type MonitorHostMetric struct {
	ID                   string `json:"id,omitempty"`
	Name                 string `json:"name,omitempty"`
	Memo                 string `json:"memo,omitempty"`
	Type                 string `json:"type,omitempty"`
	IsMute               bool   `json:"isMute,omitempty"`
	NotificationInterval uint64 `json:"notificationInterval,omitempty"`

	Metric           string  `json:"metric,omitempty"`
	Operator         string  `json:"operator,omitempty"`
	Warning          float64 `json:"warning"`
	Critical         float64 `json:"critical"`
	Duration         uint64  `json:"duration,omitempty"`
	MaxCheckAttempts uint64  `json:"maxCheckAttempts,omitempty"`

	Scopes        []string `json:"scopes,omitempty"`
	ExcludeScopes []string `json:"excludeScopes,omitempty"`
}

// MonitorType returns monitor type.
func (m *MonitorHostMetric) MonitorType() string { return monitorTypeHostMeric }

// MonitorName returns monitor name.
func (m *MonitorHostMetric) MonitorName() string { return m.Name }

// MonitorID returns monitor id.
func (m *MonitorHostMetric) MonitorID() string { return m.ID }

// MonitorServiceMetric represents service metric monitor.
type MonitorServiceMetric struct {
	ID                   string `json:"id,omitempty"`
	Name                 string `json:"name,omitempty"`
	Memo                 string `json:"memo,omitempty"`
	Type                 string `json:"type,omitempty"`
	IsMute               bool   `json:"isMute,omitempty"`
	NotificationInterval uint64 `json:"notificationInterval,omitempty"`

	Service          string  `json:"service,omitempty"`
	Metric           string  `json:"metric,omitempty"`
	Operator         string  `json:"operator,omitempty"`
	Warning          float64 `json:"warning"`
	Critical         float64 `json:"critical"`
	Duration         uint64  `json:"duration,omitempty"`
	MaxCheckAttempts uint64  `json:"maxCheckAttempts,omitempty"`
}

// MonitorType returns monitor type.
func (m *MonitorServiceMetric) MonitorType() string { return monitorTypeServiceMetric }

// MonitorName returns monitor name.
func (m *MonitorServiceMetric) MonitorName() string { return m.Name }

// MonitorID returns monitor id.
func (m *MonitorServiceMetric) MonitorID() string { return m.ID }

// MonitorExternalHTTP represents external HTTP monitor.
type MonitorExternalHTTP struct {
	ID                   string `json:"id,omitempty"`
	Name                 string `json:"name,omitempty"`
	Memo                 string `json:"memo,omitempty"`
	Type                 string `json:"type,omitempty"`
	IsMute               bool   `json:"isMute,omitempty"`
	NotificationInterval uint64 `json:"notificationInterval,omitempty"`

	Method                          string  `json:"method,omitempty"`
	URL                             string  `json:"url,omitempty"`
	MaxCheckAttempts                uint64  `json:"maxCheckAttempts,omitempty"`
	Service                         string  `json:"service,omitempty"`
	ResponseTimeCritical            float64 `json:"responseTimeCritical,omitempty"`
	ResponseTimeWarning             float64 `json:"responseTimeWarning,omitempty"`
	ResponseTimeDuration            float64 `json:"responseTimeDuration,omitempty"`
	RequestBody                     string  `json:"requestBody,omitempty"`
	ContainsString                  string  `json:"containsString,omitempty"`
	CertificationExpirationCritical uint64  `json:"certificationExpirationCritical,omitempty"`
	CertificationExpirationWarning  uint64  `json:"certificationExpirationWarning,omitempty"`
	SkipCertificateVerification     bool    `json:"skipCertificateVerification,omitempty"`
	// Empty list of headers and nil are different. You have to specify empty
	// list as headers explicitly if you want to remove all headers instead of
	// using nil.
	Headers []HeaderField `json:"headers"`
}

// HeaderField represents key-value pairs in an HTTP header for external http
// monitoring.
type HeaderField struct {
	Name  string `json:"name"`
	Value string `json:"value"`
}

// MonitorType returns monitor type.
func (m *MonitorExternalHTTP) MonitorType() string { return monitorTypeExternalHTTP }

// MonitorName returns monitor name.
func (m *MonitorExternalHTTP) MonitorName() string { return m.Name }

// MonitorID returns monitor id.
func (m *MonitorExternalHTTP) MonitorID() string { return m.ID }

// MonitorExpression represents expression monitor.
type MonitorExpression struct {
	ID                   string `json:"id,omitempty"`
	Name                 string `json:"name,omitempty"`
	Memo                 string `json:"memo,omitempty"`
	Type                 string `json:"type,omitempty"`
	IsMute               bool   `json:"isMute,omitempty"`
	NotificationInterval uint64 `json:"notificationInterval,omitempty"`

	Expression string  `json:"expression,omitempty"`
	Operator   string  `json:"operator,omitempty"`
	Warning    float64 `json:"warning"`
	Critical   float64 `json:"critical"`
}

// MonitorType returns monitor type.
func (m *MonitorExpression) MonitorType() string { return monitorTypeExpression }

// MonitorName returns monitor name.
func (m *MonitorExpression) MonitorName() string { return m.Name }

// MonitorID returns monitor id.
func (m *MonitorExpression) MonitorID() string { return m.ID }

// FindMonitors find monitors
func (c *Client) FindMonitors() ([]Monitor, error) {
	req, err := http.NewRequest("GET", c.urlFor("/api/v0/monitors").String(), nil)
	if err != nil {
		return nil, err
	}
	resp, err := c.Request(req)
	defer closeResponse(resp)
	if err != nil {
		return nil, err
	}

	var data struct {
		Monitors []json.RawMessage `json:"monitors"`
	}
	err = json.NewDecoder(resp.Body).Decode(&data)
	if err != nil {
		return nil, err
	}
	ms := make([]Monitor, 0, len(data.Monitors))
	for _, rawmes := range data.Monitors {
		m, err := decodeMonitor(rawmes)
		if err != nil {
			return nil, err
		}
		ms = append(ms, m)
	}
	return ms, err
}

// CreateMonitor creating monitor
func (c *Client) CreateMonitor(param Monitor) (Monitor, error) {
	resp, err := c.PostJSON("/api/v0/monitors", param)
	defer closeResponse(resp)
	if err != nil {
		return nil, err
	}
	return decodeMonitorReader(resp.Body)
}

// UpdateMonitor update monitor
func (c *Client) UpdateMonitor(monitorID string, param Monitor) (Monitor, error) {
	resp, err := c.PutJSON(fmt.Sprintf("/api/v0/monitors/%s", monitorID), param)
	defer closeResponse(resp)
	if err != nil {
		return nil, err
	}
	return decodeMonitorReader(resp.Body)
}

// DeleteMonitor update monitor
func (c *Client) DeleteMonitor(monitorID string) (Monitor, error) {
	req, err := http.NewRequest(
		"DELETE",
		c.urlFor(fmt.Sprintf("/api/v0/monitors/%s", monitorID)).String(),
		nil,
	)
	if err != nil {
		return nil, err
	}
	req.Header.Add("Content-Type", "application/json")

	resp, err := c.Request(req)
	defer closeResponse(resp)
	if err != nil {
		return nil, err
	}
	return decodeMonitorReader(resp.Body)
}

// decodeMonitor decodes json.RawMessage and returns monitor.
func decodeMonitor(mes json.RawMessage) (Monitor, error) {
	var typeData struct {
		Type string `json:"type"`
	}
	if err := json.Unmarshal(mes, &typeData); err != nil {
		return nil, err
	}
	var m Monitor
	switch typeData.Type {
	case monitorTypeConnectivity:
		m = &MonitorConnectivity{}
	case monitorTypeHostMeric:
		m = &MonitorHostMetric{}
	case monitorTypeServiceMetric:
		m = &MonitorServiceMetric{}
	case monitorTypeExternalHTTP:
		m = &MonitorExternalHTTP{}
	case monitorTypeExpression:
		m = &MonitorExpression{}
	}
	if err := json.Unmarshal(mes, m); err != nil {
		return nil, err
	}
	return m, nil
}

func decodeMonitorReader(r io.Reader) (Monitor, error) {
	b, err := ioutil.ReadAll(r)
	if err != nil {
		return nil, err
	}
	return decodeMonitor(b)
}
<commit_msg>change the types of warning/critical threshold to pointers of number to distinguish 0 between empty threshold<commit_after>package mackerel

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

/*
{
  "monitors": [
    {
      "id": "2cSZzK3XfmG",
      "type": "connectivity",
      "isMute": false,
      "scopes": [],
      "excludeScopes": []
    },
    {
      "id"  : "2cSZzK3XfmG",
      "type": "host",
      "isMute": false,
      "name": "disk.aa-00.writes.delta",
      "duration": 3,
      "metric": "disk.aa-00.writes.delta",
      "operator": ">",
      "warning": 20000.0,
      "critical": 400000.0,
      "scopes": [
        "SomeService"
      ],
      "excludeScopes": [
        "SomeService: db-slave-backup"
      ],
      "maxCheckAttempts": 1,
      "notificationInterval": 60
    },
    {
      "id"  : "2cSZzK3XfmG",
      "type": "service",
      "isMute": false,
      "name": "SomeService - custom.access_num.4xx_count",
      "service": "SomeService",
      "duration": 1,
      "metric": "custom.access_num.4xx_count",
      "operator": ">",
      "warning": 50.0,
      "critical": 100.0,
      "maxCheckAttempts": 1
    },
    {
      "id"  : "2cSZzK3XfmG",
      "type": "external",
      "isMute": false,
      "name": "example.com",
      "method": "GET",
      "url": "http://www.example.com",
      "service": "SomeService",
      "maxCheckAttempts": 1,
      "responseTimeCritical": 10000,
      "responseTimeWarning": 5000,
      "responseTimeDuration": 5,
      "certificationExpirationCritical": 15,
      "certificationExpirationWarning": 30,
      "requestBody": "Request Body",
      "containsString": "Example",
      "skipCertificateVerification": true,
      "headers": [
        { "name": "Cache-Control", "value": "no-cache"}
      ]
    }
  ]
}
*/

// Monitor represents interface to which each monitor type must confirm to.
type Monitor interface {
	MonitorType() string
	MonitorID() string
	MonitorName() string

	isMonitor()
}

const (
	monitorTypeConnectivity  = "connectivity"
	monitorTypeHostMeric     = "host"
	monitorTypeServiceMetric = "service"
	monitorTypeExternalHTTP  = "external"
	monitorTypeExpression    = "expression"
)

// Ensure each monitor type conforms to the Monitor interface.
var (
	_ Monitor = (*MonitorConnectivity)(nil)
	_ Monitor = (*MonitorHostMetric)(nil)
	_ Monitor = (*MonitorServiceMetric)(nil)
	_ Monitor = (*MonitorExternalHTTP)(nil)
	_ Monitor = (*MonitorExpression)(nil)
)

// Ensure only monitor types defined in this package can be assigned to the
// Monitor interface.
//
func (m *MonitorConnectivity) isMonitor()  {}
func (m *MonitorHostMetric) isMonitor()    {}
func (m *MonitorServiceMetric) isMonitor() {}
func (m *MonitorExternalHTTP) isMonitor()  {}
func (m *MonitorExpression) isMonitor()    {}

// MonitorConnectivity represents connectivity monitor.
type MonitorConnectivity struct {
	ID                   string `json:"id,omitempty"`
	Name                 string `json:"name,omitempty"`
	Memo                 string `json:"memo,omitempty"`
	Type                 string `json:"type,omitempty"`
	IsMute               bool   `json:"isMute,omitempty"`
	NotificationInterval uint64 `json:"notificationInterval,omitempty"`

	Scopes        []string `json:"scopes,omitempty"`
	ExcludeScopes []string `json:"excludeScopes,omitempty"`
}

// MonitorType returns monitor type.
func (m *MonitorConnectivity) MonitorType() string { return monitorTypeConnectivity }

// MonitorName returns monitor name.
func (m *MonitorConnectivity) MonitorName() string { return m.Name }

// MonitorID returns monitor id.
func (m *MonitorConnectivity) MonitorID() string { return m.ID }

// MonitorHostMetric represents host metric monitor.
type MonitorHostMetric struct {
	ID                   string `json:"id,omitempty"`
	Name                 string `json:"name,omitempty"`
	Memo                 string `json:"memo,omitempty"`
	Type                 string `json:"type,omitempty"`
	IsMute               bool   `json:"isMute,omitempty"`
	NotificationInterval uint64 `json:"notificationInterval,omitempty"`

	Metric           string   `json:"metric,omitempty"`
	Operator         string   `json:"operator,omitempty"`
	Warning          *float64 `json:"warning"`
	Critical         *float64 `json:"critical"`
	Duration         uint64   `json:"duration,omitempty"`
	MaxCheckAttempts uint64   `json:"maxCheckAttempts,omitempty"`

	Scopes        []string `json:"scopes,omitempty"`
	ExcludeScopes []string `json:"excludeScopes,omitempty"`
}

// MonitorType returns monitor type.
func (m *MonitorHostMetric) MonitorType() string { return monitorTypeHostMeric }

// MonitorName returns monitor name.
func (m *MonitorHostMetric) MonitorName() string { return m.Name }

// MonitorID returns monitor id.
func (m *MonitorHostMetric) MonitorID() string { return m.ID }

// MonitorServiceMetric represents service metric monitor.
type MonitorServiceMetric struct {
	ID                   string `json:"id,omitempty"`
	Name                 string `json:"name,omitempty"`
	Memo                 string `json:"memo,omitempty"`
	Type                 string `json:"type,omitempty"`
	IsMute               bool   `json:"isMute,omitempty"`
	NotificationInterval uint64 `json:"notificationInterval,omitempty"`

	Service          string   `json:"service,omitempty"`
	Metric           string   `json:"metric,omitempty"`
	Operator         string   `json:"operator,omitempty"`
	Warning          *float64 `json:"warning"`
	Critical         *float64 `json:"critical"`
	Duration         uint64   `json:"duration,omitempty"`
	MaxCheckAttempts uint64   `json:"maxCheckAttempts,omitempty"`
}

// MonitorType returns monitor type.
func (m *MonitorServiceMetric) MonitorType() string { return monitorTypeServiceMetric }

// MonitorName returns monitor name.
func (m *MonitorServiceMetric) MonitorName() string { return m.Name }

// MonitorID returns monitor id.
func (m *MonitorServiceMetric) MonitorID() string { return m.ID }

// MonitorExternalHTTP represents external HTTP monitor.
type MonitorExternalHTTP struct {
	ID                   string `json:"id,omitempty"`
	Name                 string `json:"name,omitempty"`
	Memo                 string `json:"memo,omitempty"`
	Type                 string `json:"type,omitempty"`
	IsMute               bool   `json:"isMute,omitempty"`
	NotificationInterval uint64 `json:"notificationInterval,omitempty"`

	Method                          string   `json:"method,omitempty"`
	URL                             string   `json:"url,omitempty"`
	MaxCheckAttempts                uint64   `json:"maxCheckAttempts,omitempty"`
	Service                         string   `json:"service,omitempty"`
	ResponseTimeCritical            *float64 `json:"responseTimeCritical,omitempty"`
	ResponseTimeWarning             *float64 `json:"responseTimeWarning,omitempty"`
	ResponseTimeDuration            *uint64  `json:"responseTimeDuration,omitempty"`
	RequestBody                     string   `json:"requestBody,omitempty"`
	ContainsString                  string   `json:"containsString,omitempty"`
	CertificationExpirationCritical *uint64  `json:"certificationExpirationCritical,omitempty"`
	CertificationExpirationWarning  *uint64  `json:"certificationExpirationWarning,omitempty"`
	SkipCertificateVerification     bool     `json:"skipCertificateVerification,omitempty"`
	// Empty list of headers and nil are different. You have to specify empty
	// list as headers explicitly if you want to remove all headers instead of
	// using nil.
	Headers []HeaderField `json:"headers"`
}

// HeaderField represents key-value pairs in an HTTP header for external http
// monitoring.
type HeaderField struct {
	Name  string `json:"name"`
	Value string `json:"value"`
}

// MonitorType returns monitor type.
func (m *MonitorExternalHTTP) MonitorType() string { return monitorTypeExternalHTTP }

// MonitorName returns monitor name.
func (m *MonitorExternalHTTP) MonitorName() string { return m.Name }

// MonitorID returns monitor id.
func (m *MonitorExternalHTTP) MonitorID() string { return m.ID }

// MonitorExpression represents expression monitor.
type MonitorExpression struct {
	ID                   string `json:"id,omitempty"`
	Name                 string `json:"name,omitempty"`
	Memo                 string `json:"memo,omitempty"`
	Type                 string `json:"type,omitempty"`
	IsMute               bool   `json:"isMute,omitempty"`
	NotificationInterval uint64 `json:"notificationInterval,omitempty"`

	Expression string   `json:"expression,omitempty"`
	Operator   string   `json:"operator,omitempty"`
	Warning    *float64 `json:"warning"`
	Critical   *float64 `json:"critical"`
}

// MonitorType returns monitor type.
func (m *MonitorExpression) MonitorType() string { return monitorTypeExpression }

// MonitorName returns monitor name.
func (m *MonitorExpression) MonitorName() string { return m.Name }

// MonitorID returns monitor id.
func (m *MonitorExpression) MonitorID() string { return m.ID }

// FindMonitors find monitors
func (c *Client) FindMonitors() ([]Monitor, error) {
	req, err := http.NewRequest("GET", c.urlFor("/api/v0/monitors").String(), nil)
	if err != nil {
		return nil, err
	}
	resp, err := c.Request(req)
	defer closeResponse(resp)
	if err != nil {
		return nil, err
	}

	var data struct {
		Monitors []json.RawMessage `json:"monitors"`
	}
	err = json.NewDecoder(resp.Body).Decode(&data)
	if err != nil {
		return nil, err
	}
	ms := make([]Monitor, 0, len(data.Monitors))
	for _, rawmes := range data.Monitors {
		m, err := decodeMonitor(rawmes)
		if err != nil {
			return nil, err
		}
		ms = append(ms, m)
	}
	return ms, err
}

// CreateMonitor creating monitor
func (c *Client) CreateMonitor(param Monitor) (Monitor, error) {
	resp, err := c.PostJSON("/api/v0/monitors", param)
	defer closeResponse(resp)
	if err != nil {
		return nil, err
	}
	return decodeMonitorReader(resp.Body)
}

// UpdateMonitor update monitor
func (c *Client) UpdateMonitor(monitorID string, param Monitor) (Monitor, error) {
	resp, err := c.PutJSON(fmt.Sprintf("/api/v0/monitors/%s", monitorID), param)
	defer closeResponse(resp)
	if err != nil {
		return nil, err
	}
	return decodeMonitorReader(resp.Body)
}

// DeleteMonitor update monitor
func (c *Client) DeleteMonitor(monitorID string) (Monitor, error) {
	req, err := http.NewRequest(
		"DELETE",
		c.urlFor(fmt.Sprintf("/api/v0/monitors/%s", monitorID)).String(),
		nil,
	)
	if err != nil {
		return nil, err
	}
	req.Header.Add("Content-Type", "application/json")

	resp, err := c.Request(req)
	defer closeResponse(resp)
	if err != nil {
		return nil, err
	}
	return decodeMonitorReader(resp.Body)
}

// decodeMonitor decodes json.RawMessage and returns monitor.
func decodeMonitor(mes json.RawMessage) (Monitor, error) {
	var typeData struct {
		Type string `json:"type"`
	}
	if err := json.Unmarshal(mes, &typeData); err != nil {
		return nil, err
	}
	var m Monitor
	switch typeData.Type {
	case monitorTypeConnectivity:
		m = &MonitorConnectivity{}
	case monitorTypeHostMeric:
		m = &MonitorHostMetric{}
	case monitorTypeServiceMetric:
		m = &MonitorServiceMetric{}
	case monitorTypeExternalHTTP:
		m = &MonitorExternalHTTP{}
	case monitorTypeExpression:
		m = &MonitorExpression{}
	}
	if err := json.Unmarshal(mes, m); err != nil {
		return nil, err
	}
	return m, nil
}

func decodeMonitorReader(r io.Reader) (Monitor, error) {
	b, err := ioutil.ReadAll(r)
	if err != nil {
		return nil, err
	}
	return decodeMonitor(b)
}
<|endoftext|>


<commit_before>// Package besticon includes functions
// finding icons for a given web site.
package besticon

import (
	"bytes"
	"crypto/sha1"
	"errors"
	"fmt"
	"image"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/cookiejar"
	"net/url"
	"os"
	"sort"
	"strings"
	"time"

	"image/color"

	// Load supported image formats.
	_ "image/gif"
	_ "image/png"

	"github.com/mat/besticon/colorfinder"

	// ...even more image formats.
	_ "github.com/mat/besticon/ico"

	"github.com/PuerkitoBio/goquery"
	"golang.org/x/net/html/charset"
	"golang.org/x/net/idna"
	"golang.org/x/net/publicsuffix"
)

var defaultFormats []string

const MinIconSize = 10
const MaxIconSize = 500

// Icon holds icon information.
type Icon struct {
	URL       string `json:"url"`
	Width     int    `json:"width"`
	Height    int    `json:"height"`
	Format    string `json:"format"`
	Bytes     int    `json:"bytes"`
	Error     error  `json:"error"`
	Sha1sum   string `json:"sha1sum"`
	ImageData []byte `json:",omitempty"`
}

type IconFinder struct {
	FormatsAllowed []string
	KeepImageBytes bool
	icons          []Icon
}

func (f *IconFinder) FetchIcons(url string) ([]Icon, error) {
	var err error

	if CacheEnabled() {
		f.icons, err = resultFromCache(url)
	} else {
		f.icons, err = fetchIcons(url)
	}

	return f.Icons(), err
}

func (f *IconFinder) IconWithMinSize(minSize int) *Icon {
	SortIcons(f.icons, false)

	for _, ico := range f.icons {
		if ico.Width >= minSize && ico.Height >= minSize {
			return &ico
		}
	}
	return nil
}

func (f *IconFinder) MainColorForIcons() *color.RGBA {
	return MainColorForIcons(f.icons)
}

func (f *IconFinder) Icons() []Icon {
	return discardUnwantedFormats(f.icons, f.FormatsAllowed)
}

func (ico *Icon) Image() (*image.Image, error) {
	img, _, err := image.Decode(bytes.NewReader(ico.ImageData))
	return &img, err
}

func discardUnwantedFormats(icons []Icon, wantedFormats []string) []Icon {
	formats := defaultFormats
	if len(wantedFormats) > 0 {
		formats = wantedFormats
	}

	return filterIcons(icons, func(ico Icon) bool {
		return includesString(formats, ico.Format)
	})
}

type iconPredicate func(Icon) bool

func filterIcons(icons []Icon, pred iconPredicate) []Icon {
	result := []Icon{}
	for _, ico := range icons {
		if pred(ico) {
			result = append(result, ico)
		}
	}
	return result
}

func includesString(arr []string, str string) bool {
	for _, e := range arr {
		if e == str {
			return true
		}
	}
	return false
}

func fetchIcons(siteURL string) ([]Icon, error) {
	siteURL = strings.TrimSpace(siteURL)
	if !strings.HasPrefix(siteURL, "http") {
		siteURL = "http://" + siteURL
	}

	html, url, e := fetchHTML(siteURL)
	if e != nil {
		return nil, e
	}

	links, e := findIconLinks(url, html)
	if e != nil {
		return nil, e
	}

	icons := fetchAllIcons(links)
	icons = rejectBrokenIcons(icons)
	SortIcons(icons, true)

	return icons, nil
}

const maxResponseBodySize = 10485760 // 10MB

func fetchHTML(url string) ([]byte, *url.URL, error) {
	r, e := get(url)
	if e != nil {
		return nil, nil, e
	}

	if !(r.StatusCode >= 200 && r.StatusCode < 300) {
		return nil, nil, errors.New("besticon: not found")
	}

	b, e := getBodyBytes(r)
	if e != nil {
		return nil, nil, e
	}
	if len(b) == 0 {
		return nil, nil, errors.New("besticon: empty response")
	}

	reader := bytes.NewReader(b)
	contentType := r.Header.Get("Content-Type")
	utf8reader, e := charset.NewReader(reader, contentType)
	if e != nil {
		return nil, nil, e
	}
	utf8bytes, e := ioutil.ReadAll(utf8reader)
	if e != nil {
		return nil, nil, e
	}

	return utf8bytes, r.Request.URL, nil
}

var iconPaths = []string{
	"/favicon.ico",
	"/apple-touch-icon.png",
	"/apple-touch-icon-precomposed.png",
}

type empty struct{}

func findIconLinks(siteURL *url.URL, html []byte) ([]string, error) {
	doc, e := docFromHTML(html)
	if e != nil {
		return nil, e
	}

	baseURL := determineBaseURL(siteURL, doc)
	links := make(map[string]empty)

	// Add common, hard coded icon paths
	for _, path := range iconPaths {
		links[urlFromBase(baseURL, path)] = empty{}
	}

	// Add icons found in page
	urls := extractIconTags(doc)
	for _, url := range urls {
		url, e := absoluteURL(baseURL, url)
		if e == nil {
			links[url] = empty{}
		}
	}

	// Turn unique keys into array
	result := []string{}
	for u := range links {
		result = append(result, u)
	}
	sort.Strings(result)

	return result, nil
}

func determineBaseURL(siteURL *url.URL, doc *goquery.Document) *url.URL {
	baseTagHref := extractBaseTag(doc)
	if baseTagHref != "" {
		baseTagURL, e := url.Parse(baseTagHref)
		if e != nil {
			return siteURL
		}
		return baseTagURL
	}

	return siteURL
}

func docFromHTML(html []byte) (*goquery.Document, error) {
	doc, e := goquery.NewDocumentFromReader(bytes.NewReader(html))
	if e != nil || doc == nil {
		return nil, errParseHTML
	}
	return doc, nil
}

var csspaths = strings.Join([]string{
	"link[rel='icon']",
	"link[rel='shortcut icon']",
	"link[rel='apple-touch-icon']",
	"link[rel='apple-touch-icon-precomposed']",
}, ", ")

var errParseHTML = errors.New("besticon: could not parse html")

func extractBaseTag(doc *goquery.Document) string {
	href := ""
	doc.Find("head base[href]").First().Each(func(i int, s *goquery.Selection) {
		href, _ = s.Attr("href")
	})
	return href
}

func extractIconTags(doc *goquery.Document) []string {
	hits := []string{}
	doc.Find(csspaths).Each(func(i int, s *goquery.Selection) {
		href, ok := s.Attr("href")
		if ok && href != "" {
			hits = append(hits, href)
		}
	})
	return hits
}

func MainColorForIcons(icons []Icon) *color.RGBA {
	if len(icons) == 0 {
		return nil
	}

	var icon *Icon
	for _, ico := range icons {
		if ico.Format == "png" || ico.Format == "gif" {
			icon = &ico
			break
		}
	}
	if icon == nil {
		return nil
	}

	img, err := icon.Image()
	if err != nil {
		return nil
	}

	cf := colorfinder.ColorFinder{}
	color, err := cf.FindMainColor(*img)
	if err != nil {
		return nil
	}

	return &color
}

func fetchAllIcons(urls []string) []Icon {
	ch := make(chan Icon)

	for _, u := range urls {
		go func(u string) { ch <- fetchIconDetails(u) }(u)
	}

	icons := []Icon{}
	for range urls {
		icon := <-ch
		icons = append(icons, icon)
	}
	return icons
}

func fetchIconDetails(url string) Icon {
	i := Icon{URL: url}

	response, e := get(url)
	if e != nil {
		i.Error = e
		return i
	}

	b, e := getBodyBytes(response)
	if e != nil {
		i.Error = e
		return i
	}

	cfg, format, e := image.DecodeConfig(bytes.NewReader(b))
	if e != nil {
		i.Error = fmt.Errorf("besticon: unknown image format: %s", e)
		return i
	}

	i.Width = cfg.Width
	i.Height = cfg.Height
	i.Format = format
	i.Bytes = len(b)
	i.Sha1sum = sha1Sum(b)
	if keepImageBytes {
		i.ImageData = b
	}

	return i
}

func get(urlstring string) (*http.Response, error) {
	u, e := url.Parse(urlstring)
	if e != nil {
		return nil, e
	}
	// Maybe we can get rid of this conversion someday
	// https://github.com/golang/go/issues/13835
	u.Host, e = idna.ToASCII(u.Host)
	if e != nil {
		return nil, e
	}

	req, e := http.NewRequest("GET", u.String(), nil)
	if e != nil {
		return nil, e
	}

	setDefaultHeaders(req)

	start := time.Now()
	resp, err := client.Do(req)
	end := time.Now()
	duration := end.Sub(start)

	if err != nil {
		logger.Printf("Error: %s %s %s %.2fms",
			req.Method,
			req.URL,
			err,
			float64(duration)/float64(time.Millisecond),
		)
	} else {
		logger.Printf("%s %s %d %.2fms %d",
			req.Method,
			req.URL,
			resp.StatusCode,
			float64(duration)/float64(time.Millisecond),
			resp.ContentLength,
		)
	}

	return resp, err
}

func getBodyBytes(r *http.Response) ([]byte, error) {
	limitReader := io.LimitReader(r.Body, maxResponseBodySize)
	b, e := ioutil.ReadAll(limitReader)
	r.Body.Close()

	if len(b) >= maxResponseBodySize {
		return nil, errors.New("body too large")
	}
	return b, e
}

func setDefaultHeaders(req *http.Request) {
	req.Header.Set("Accept", "*/*")
	req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/534.55.3 (KHTML, like Gecko) Version/5.1.3 Safari/534.53.10")
}

func mustInitCookieJar() *cookiejar.Jar {
	options := cookiejar.Options{
		PublicSuffixList: publicsuffix.List,
	}
	jar, e := cookiejar.New(&options)
	if e != nil {
		panic(e)
	}

	return jar
}

func checkRedirect(req *http.Request, via []*http.Request) error {
	setDefaultHeaders(req)

	if len(via) >= 10 {
		return errors.New("stopped after 10 redirects")
	}
	return nil
}

func absoluteURL(baseURL *url.URL, path string) (string, error) {
	url, e := url.Parse(path)
	if e != nil {
		return "", e
	}

	url.Scheme = baseURL.Scheme
	if url.Scheme == "" {
		url.Scheme = "http"
	}

	if url.Host == "" {
		url.Host = baseURL.Host
	}
	return url.String(), nil
}

func urlFromBase(baseURL *url.URL, path string) string {
	url := *baseURL
	url.Path = path
	if url.Scheme == "" {
		url.Scheme = "http"
	}

	return url.String()
}

func rejectBrokenIcons(icons []Icon) []Icon {
	result := []Icon{}
	for _, img := range icons {
		if img.Error == nil && (img.Width > 1 && img.Height > 1) {
			result = append(result, img)
		}
	}
	return result
}

func sha1Sum(b []byte) string {
	hash := sha1.New()
	hash.Write(b)
	bs := hash.Sum(nil)
	return fmt.Sprintf("%x", bs)
}

var client *http.Client
var keepImageBytes bool

func init() {
	setHTTPClient(&http.Client{Timeout: 20 * time.Second})

	// Needs to be kept in sync with those image/... imports
	defaultFormats = []string{"png", "gif", "ico"}
}

func setHTTPClient(c *http.Client) {
	c.Jar = mustInitCookieJar()
	c.CheckRedirect = checkRedirect
	client = c
}

var logger *log.Logger

// SetLogOutput sets the output for the package's logger.
func SetLogOutput(w io.Writer) {
	logger = log.New(w, "http:  ", log.LstdFlags|log.Lmicroseconds)
}

func init() {
	SetLogOutput(os.Stdout)
	keepImageBytes = true
}

var BuildDate string // set via ldflags on Make
<commit_msg>Change User Agent for fewer grey icons #12 (thanks @mmkal)<commit_after>// Package besticon includes functions
// finding icons for a given web site.
package besticon

import (
	"bytes"
	"crypto/sha1"
	"errors"
	"fmt"
	"image"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/cookiejar"
	"net/url"
	"os"
	"sort"
	"strings"
	"time"

	"image/color"

	// Load supported image formats.
	_ "image/gif"
	_ "image/png"

	"github.com/mat/besticon/colorfinder"

	// ...even more image formats.
	_ "github.com/mat/besticon/ico"

	"github.com/PuerkitoBio/goquery"
	"golang.org/x/net/html/charset"
	"golang.org/x/net/idna"
	"golang.org/x/net/publicsuffix"
)

var defaultFormats []string

const MinIconSize = 10
const MaxIconSize = 500

// Icon holds icon information.
type Icon struct {
	URL       string `json:"url"`
	Width     int    `json:"width"`
	Height    int    `json:"height"`
	Format    string `json:"format"`
	Bytes     int    `json:"bytes"`
	Error     error  `json:"error"`
	Sha1sum   string `json:"sha1sum"`
	ImageData []byte `json:",omitempty"`
}

type IconFinder struct {
	FormatsAllowed []string
	KeepImageBytes bool
	icons          []Icon
}

func (f *IconFinder) FetchIcons(url string) ([]Icon, error) {
	var err error

	if CacheEnabled() {
		f.icons, err = resultFromCache(url)
	} else {
		f.icons, err = fetchIcons(url)
	}

	return f.Icons(), err
}

func (f *IconFinder) IconWithMinSize(minSize int) *Icon {
	SortIcons(f.icons, false)

	for _, ico := range f.icons {
		if ico.Width >= minSize && ico.Height >= minSize {
			return &ico
		}
	}
	return nil
}

func (f *IconFinder) MainColorForIcons() *color.RGBA {
	return MainColorForIcons(f.icons)
}

func (f *IconFinder) Icons() []Icon {
	return discardUnwantedFormats(f.icons, f.FormatsAllowed)
}

func (ico *Icon) Image() (*image.Image, error) {
	img, _, err := image.Decode(bytes.NewReader(ico.ImageData))
	return &img, err
}

func discardUnwantedFormats(icons []Icon, wantedFormats []string) []Icon {
	formats := defaultFormats
	if len(wantedFormats) > 0 {
		formats = wantedFormats
	}

	return filterIcons(icons, func(ico Icon) bool {
		return includesString(formats, ico.Format)
	})
}

type iconPredicate func(Icon) bool

func filterIcons(icons []Icon, pred iconPredicate) []Icon {
	result := []Icon{}
	for _, ico := range icons {
		if pred(ico) {
			result = append(result, ico)
		}
	}
	return result
}

func includesString(arr []string, str string) bool {
	for _, e := range arr {
		if e == str {
			return true
		}
	}
	return false
}

func fetchIcons(siteURL string) ([]Icon, error) {
	siteURL = strings.TrimSpace(siteURL)
	if !strings.HasPrefix(siteURL, "http") {
		siteURL = "http://" + siteURL
	}

	html, url, e := fetchHTML(siteURL)
	if e != nil {
		return nil, e
	}

	links, e := findIconLinks(url, html)
	if e != nil {
		return nil, e
	}

	icons := fetchAllIcons(links)
	icons = rejectBrokenIcons(icons)
	SortIcons(icons, true)

	return icons, nil
}

const maxResponseBodySize = 10485760 // 10MB

func fetchHTML(url string) ([]byte, *url.URL, error) {
	r, e := get(url)
	if e != nil {
		return nil, nil, e
	}

	if !(r.StatusCode >= 200 && r.StatusCode < 300) {
		return nil, nil, errors.New("besticon: not found")
	}

	b, e := getBodyBytes(r)
	if e != nil {
		return nil, nil, e
	}
	if len(b) == 0 {
		return nil, nil, errors.New("besticon: empty response")
	}

	reader := bytes.NewReader(b)
	contentType := r.Header.Get("Content-Type")
	utf8reader, e := charset.NewReader(reader, contentType)
	if e != nil {
		return nil, nil, e
	}
	utf8bytes, e := ioutil.ReadAll(utf8reader)
	if e != nil {
		return nil, nil, e
	}

	return utf8bytes, r.Request.URL, nil
}

var iconPaths = []string{
	"/favicon.ico",
	"/apple-touch-icon.png",
	"/apple-touch-icon-precomposed.png",
}

type empty struct{}

func findIconLinks(siteURL *url.URL, html []byte) ([]string, error) {
	doc, e := docFromHTML(html)
	if e != nil {
		return nil, e
	}

	baseURL := determineBaseURL(siteURL, doc)
	links := make(map[string]empty)

	// Add common, hard coded icon paths
	for _, path := range iconPaths {
		links[urlFromBase(baseURL, path)] = empty{}
	}

	// Add icons found in page
	urls := extractIconTags(doc)
	for _, url := range urls {
		url, e := absoluteURL(baseURL, url)
		if e == nil {
			links[url] = empty{}
		}
	}

	// Turn unique keys into array
	result := []string{}
	for u := range links {
		result = append(result, u)
	}
	sort.Strings(result)

	return result, nil
}

func determineBaseURL(siteURL *url.URL, doc *goquery.Document) *url.URL {
	baseTagHref := extractBaseTag(doc)
	if baseTagHref != "" {
		baseTagURL, e := url.Parse(baseTagHref)
		if e != nil {
			return siteURL
		}
		return baseTagURL
	}

	return siteURL
}

func docFromHTML(html []byte) (*goquery.Document, error) {
	doc, e := goquery.NewDocumentFromReader(bytes.NewReader(html))
	if e != nil || doc == nil {
		return nil, errParseHTML
	}
	return doc, nil
}

var csspaths = strings.Join([]string{
	"link[rel='icon']",
	"link[rel='shortcut icon']",
	"link[rel='apple-touch-icon']",
	"link[rel='apple-touch-icon-precomposed']",
}, ", ")

var errParseHTML = errors.New("besticon: could not parse html")

func extractBaseTag(doc *goquery.Document) string {
	href := ""
	doc.Find("head base[href]").First().Each(func(i int, s *goquery.Selection) {
		href, _ = s.Attr("href")
	})
	return href
}

func extractIconTags(doc *goquery.Document) []string {
	hits := []string{}
	doc.Find(csspaths).Each(func(i int, s *goquery.Selection) {
		href, ok := s.Attr("href")
		if ok && href != "" {
			hits = append(hits, href)
		}
	})
	return hits
}

func MainColorForIcons(icons []Icon) *color.RGBA {
	if len(icons) == 0 {
		return nil
	}

	var icon *Icon
	for _, ico := range icons {
		if ico.Format == "png" || ico.Format == "gif" {
			icon = &ico
			break
		}
	}
	if icon == nil {
		return nil
	}

	img, err := icon.Image()
	if err != nil {
		return nil
	}

	cf := colorfinder.ColorFinder{}
	color, err := cf.FindMainColor(*img)
	if err != nil {
		return nil
	}

	return &color
}

func fetchAllIcons(urls []string) []Icon {
	ch := make(chan Icon)

	for _, u := range urls {
		go func(u string) { ch <- fetchIconDetails(u) }(u)
	}

	icons := []Icon{}
	for range urls {
		icon := <-ch
		icons = append(icons, icon)
	}
	return icons
}

func fetchIconDetails(url string) Icon {
	i := Icon{URL: url}

	response, e := get(url)
	if e != nil {
		i.Error = e
		return i
	}

	b, e := getBodyBytes(response)
	if e != nil {
		i.Error = e
		return i
	}

	cfg, format, e := image.DecodeConfig(bytes.NewReader(b))
	if e != nil {
		i.Error = fmt.Errorf("besticon: unknown image format: %s", e)
		return i
	}

	i.Width = cfg.Width
	i.Height = cfg.Height
	i.Format = format
	i.Bytes = len(b)
	i.Sha1sum = sha1Sum(b)
	if keepImageBytes {
		i.ImageData = b
	}

	return i
}

func get(urlstring string) (*http.Response, error) {
	u, e := url.Parse(urlstring)
	if e != nil {
		return nil, e
	}
	// Maybe we can get rid of this conversion someday
	// https://github.com/golang/go/issues/13835
	u.Host, e = idna.ToASCII(u.Host)
	if e != nil {
		return nil, e
	}

	req, e := http.NewRequest("GET", u.String(), nil)
	if e != nil {
		return nil, e
	}

	setDefaultHeaders(req)

	start := time.Now()
	resp, err := client.Do(req)
	end := time.Now()
	duration := end.Sub(start)

	if err != nil {
		logger.Printf("Error: %s %s %s %.2fms",
			req.Method,
			req.URL,
			err,
			float64(duration)/float64(time.Millisecond),
		)
	} else {
		logger.Printf("%s %s %d %.2fms %d",
			req.Method,
			req.URL,
			resp.StatusCode,
			float64(duration)/float64(time.Millisecond),
			resp.ContentLength,
		)
	}

	return resp, err
}

func getBodyBytes(r *http.Response) ([]byte, error) {
	limitReader := io.LimitReader(r.Body, maxResponseBodySize)
	b, e := ioutil.ReadAll(limitReader)
	r.Body.Close()

	if len(b) >= maxResponseBodySize {
		return nil, errors.New("body too large")
	}
	return b, e
}

func setDefaultHeaders(req *http.Request) {
	req.Header.Set("Accept", "*/*")
	req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36")
}

func mustInitCookieJar() *cookiejar.Jar {
	options := cookiejar.Options{
		PublicSuffixList: publicsuffix.List,
	}
	jar, e := cookiejar.New(&options)
	if e != nil {
		panic(e)
	}

	return jar
}

func checkRedirect(req *http.Request, via []*http.Request) error {
	setDefaultHeaders(req)

	if len(via) >= 10 {
		return errors.New("stopped after 10 redirects")
	}
	return nil
}

func absoluteURL(baseURL *url.URL, path string) (string, error) {
	url, e := url.Parse(path)
	if e != nil {
		return "", e
	}

	url.Scheme = baseURL.Scheme
	if url.Scheme == "" {
		url.Scheme = "http"
	}

	if url.Host == "" {
		url.Host = baseURL.Host
	}
	return url.String(), nil
}

func urlFromBase(baseURL *url.URL, path string) string {
	url := *baseURL
	url.Path = path
	if url.Scheme == "" {
		url.Scheme = "http"
	}

	return url.String()
}

func rejectBrokenIcons(icons []Icon) []Icon {
	result := []Icon{}
	for _, img := range icons {
		if img.Error == nil && (img.Width > 1 && img.Height > 1) {
			result = append(result, img)
		}
	}
	return result
}

func sha1Sum(b []byte) string {
	hash := sha1.New()
	hash.Write(b)
	bs := hash.Sum(nil)
	return fmt.Sprintf("%x", bs)
}

var client *http.Client
var keepImageBytes bool

func init() {
	setHTTPClient(&http.Client{Timeout: 20 * time.Second})

	// Needs to be kept in sync with those image/... imports
	defaultFormats = []string{"png", "gif", "ico"}
}

func setHTTPClient(c *http.Client) {
	c.Jar = mustInitCookieJar()
	c.CheckRedirect = checkRedirect
	client = c
}

var logger *log.Logger

// SetLogOutput sets the output for the package's logger.
func SetLogOutput(w io.Writer) {
	logger = log.New(w, "http:  ", log.LstdFlags|log.Lmicroseconds)
}

func init() {
	SetLogOutput(os.Stdout)
	keepImageBytes = true
}

var BuildDate string // set via ldflags on Make
<|endoftext|>


<commit_before>/*
Connects to the adb server to listen for new devices, and mounts devices under a certain directory
when connected.
*/
package main

import (
	"fmt"
	"io"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strings"

	"github.com/Sirupsen/logrus"
	"github.com/zach-klippenstein/adbfs/cli"
	"github.com/zach-klippenstein/goadb"
	"gopkg.in/alecthomas/kingpin.v2"
)

var (
	mountRoot = kingpin.Flag("root",
		"Directory in which to mount devices.").Short('r').PlaceHolder("/mnt").ExistingDir()
	adbfsPath = kingpin.Flag("adbfs",
		"Path to adbfs executable. If not specified, PATH is searched.").PlaceHolder("/usr/bin/adbfs").ExistingFile()
	allowAnyAdbfs = kingpin.Flag("disable-adbfs-verify",
		"If true, the build SHA of adbfs won't be required to match that of this executable.").Bool()

	log *logrus.Logger
)

type Config struct {
	adbfsBaseCommand exec.Cmd
	mountRoot        string

	// Permissions for mountpoint directories.
	mountpointPerm os.FileMode
}

// When creating directory names from device info, all special characters are replaced
// with single underscores. See main_test.go for examples.
var dirNameCleanerRegexp = regexp.MustCompilePOSIX(`[^-[:alnum:]]+`)

func main() {
	cli.Initialize("adbfs-automount")
	log = cli.Config.Logger

	config := &Config{
		adbfsBaseCommand: initializeAdbfsCommand(*adbfsPath),
		mountRoot:        initializeMountRoot(*mountRoot),
		mountpointPerm:   0700,
	}

	deviceWatcher := goadb.NewDeviceWatcher(cli.Config.ClientConfig())
	defer deviceWatcher.Shutdown()

	log.Info("automounter ready.")

	for {
		select {
		case event := <-deviceWatcher.C():
			if event.CameOnline() {
				log.Debugln("device connected:", event.Serial)
				go mountDevice(config, event.Serial)
			}
		}
	}
}

func initializeAdbfsCommand(path string) exec.Cmd {
	if path == "" {
		var err error
		path, err = exec.LookPath("adbfs")
		if err != nil {
			log.Fatalln("couldn't find adbfs executable in PATH.", err)
		}
	}

	log.Debugln("trying to use adbfs at", path)

	if !*allowAnyAdbfs {
		// Make sure we've got something that looks like adbfs with the right version.
		cli.CheckExecutableSameBuildSHA("adbfs", path)
	}

	log.Infoln("using adbfs executable", path)

	return exec.Cmd{
		Path:   path,
		Stdout: os.Stdout,
		Stderr: os.Stderr,
	}
}

func initializeMountRoot(path string) string {
	if path == "" {
		log.Debug("no mount root specified, falling back to default…")
		path = cli.FindDefaultMountRoot()
	}

	validateMountRoot(path)
	log.Infoln("using mount root", path)
	return path
}

func validateMountRoot(path string) {
	if path == "" {
		log.Fatalln("no mount root specified.")
	}

	info, err := os.Stat(path)
	if err != nil {
		log.Fatalln("could not read mount root", path, ":", err)
	}
	if !info.IsDir() {
		log.Fatalln(path, "is not a directory")
	}

	dir, err := os.Open(path)
	if err != nil {
		log.Fatalf("could not read mount root %s: %s", path, err)
	}

	// Only care if there are >0 entries, so don't read them all.
	entries, err := dir.Readdirnames(1)
	if err != nil && err != io.EOF {
		log.Fatalln("could not read mount root", path, ":", err)
	}
	if len(entries) != 0 {
		log.Warnln("mount root", path, "is not empty, is another instance already running?")
	}
}

func mountDevice(config *Config, serial string) {
	defer func() {
		log.Debugln("device mount process finished:", serial)
	}()

	deviceMountpoint, err := createMountpointForDevice(config, serial)
	if err != nil {
		log.Errorf("error creating mountpoint for %s: %s", serial, err)
		return
	}
	defer removeMountpoint(deviceMountpoint)

	log.Infof("mounting %s on %s", serial, deviceMountpoint)
	cmd := config.buildMountCommandForDevice(deviceMountpoint, serial)

	log.Debugf("launching adbfs: %s %s", cmd.Path, strings.Join(cmd.Args, " "))
	if err = cmd.Start(); err != nil {
		log.Errorln("error starting adbfs process:", err)
		return
	}

	log.Infof("device %s mounted with PID %d", serial, cmd.Process.Pid)

	if err = cmd.Wait(); err != nil {
		if err, ok := err.(*exec.ExitError); ok {
			log.Errorf("adbfs exited with %+v", err)
		} else {
			log.Errorf("lost connection with adbfs process:", err)
		}
		return
	}

	log.Infof("mount process for device %s stopped", serial)
}

func createMountpointForDevice(config *Config, serial string) (mountpoint string, err error) {
	adbClient := goadb.NewDeviceClient(cli.Config.ClientConfig(), goadb.DeviceWithSerial(serial))
	deviceInfo, err := adbClient.GetDeviceInfo()
	if err != nil {
		return
	}

	dirName := buildDirNameForDevice(deviceInfo)
	mountpoint = filepath.Join(config.mountRoot, dirName)

	if doesFileExist(mountpoint) {
		err = fmt.Errorf("directory exists: %s", serial, mountpoint)
		return
	}

	log.Debugf("creating %s with permissions %s", mountpoint, config.mountpointPerm)
	err = os.Mkdir(mountpoint, config.mountpointPerm)
	return
}

func removeMountpoint(path string) {
	log.Debugln("removing mountpoint", path)
	if err := os.Remove(path); err != nil {
		log.Errorf("error removing mountpoint %s: %s", path, err)
	} else {
		log.Debugln("mountpoint removed successfully.")
	}
}

func doesFileExist(path string) bool {
	_, err := os.Stat(path)
	return err == os.ErrNotExist
}

func buildDirNameForDevice(deviceInfo *goadb.DeviceInfo) string {
	rawName := fmt.Sprintf("%s-%s", deviceInfo.Model, deviceInfo.Serial)
	return dirNameCleanerRegexp.ReplaceAllLiteralString(rawName, "_")
}

func (c *Config) buildMountCommandForDevice(mountpoint, serial string) (cmd exec.Cmd) {
	// Copy the base command, don't mutate it.
	cmd = c.adbfsBaseCommand
	cmd.Args = append(cli.Config.AsArgs(),
		fmt.Sprintf("--device=%s", serial),
		fmt.Sprintf("--mountpoint=%s", mountpoint))
	return
}
<commit_msg>Remove check for empty mount root.<commit_after>/*
Connects to the adb server to listen for new devices, and mounts devices under a certain directory
when connected.
*/
package main

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strings"

	"github.com/Sirupsen/logrus"
	"github.com/zach-klippenstein/adbfs/cli"
	"github.com/zach-klippenstein/goadb"
	"gopkg.in/alecthomas/kingpin.v2"
)

var (
	mountRoot = kingpin.Flag("root",
		"Directory in which to mount devices.").Short('r').PlaceHolder("/mnt").ExistingDir()
	adbfsPath = kingpin.Flag("adbfs",
		"Path to adbfs executable. If not specified, PATH is searched.").PlaceHolder("/usr/bin/adbfs").ExistingFile()
	allowAnyAdbfs = kingpin.Flag("disable-adbfs-verify",
		"If true, the build SHA of adbfs won't be required to match that of this executable.").Hidden().Bool()

	log *logrus.Logger
)

type Config struct {
	adbfsBaseCommand exec.Cmd
	mountRoot        string

	// Permissions for mountpoint directories.
	mountpointPerm os.FileMode
}

// When creating directory names from device info, all special characters are replaced
// with single underscores. See main_test.go for examples.
var dirNameCleanerRegexp = regexp.MustCompilePOSIX(`[^-[:alnum:]]+`)

func main() {
	cli.Initialize("adbfs-automount")
	log = cli.Config.Logger

	config := &Config{
		adbfsBaseCommand: initializeAdbfsCommand(*adbfsPath),
		mountRoot:        initializeMountRoot(*mountRoot),
		mountpointPerm:   0700,
	}

	deviceWatcher := goadb.NewDeviceWatcher(cli.Config.ClientConfig())
	defer deviceWatcher.Shutdown()

	log.Info("automounter ready.")

	for {
		select {
		case event := <-deviceWatcher.C():
			if event.CameOnline() {
				log.Debugln("device connected:", event.Serial)
				go mountDevice(config, event.Serial)
			}
		}
	}
}

func initializeAdbfsCommand(path string) exec.Cmd {
	if path == "" {
		var err error
		path, err = exec.LookPath("adbfs")
		if err != nil {
			log.Fatalln("couldn't find adbfs executable in PATH.", err)
		}
	}

	log.Debugln("trying to use adbfs at", path)

	if !*allowAnyAdbfs {
		// Make sure we've got something that looks like adbfs with the right version.
		cli.CheckExecutableSameBuildSHA("adbfs", path)
	}

	log.Infoln("using adbfs executable", path)

	return exec.Cmd{
		Path:   path,
		Stdout: os.Stdout,
		Stderr: os.Stderr,
	}
}

func initializeMountRoot(path string) string {
	if path == "" {
		log.Debug("no mount root specified, falling back to default…")
		path = cli.FindDefaultMountRoot()
	}

	validateMountRoot(path)
	log.Infoln("using mount root", path)
	return path
}

func validateMountRoot(path string) {
	if path == "" {
		log.Fatalln("no mount root specified.")
	}

	info, err := os.Stat(path)
	if err != nil {
		log.Fatalln("could not read mount root", path, ":", err)
	}
	if !info.IsDir() {
		log.Fatalln(path, "is not a directory")
	}
}

func mountDevice(config *Config, serial string) {
	defer func() {
		log.Debugln("device mount process finished:", serial)
	}()

	deviceMountpoint, err := createMountpointForDevice(config, serial)
	if err != nil {
		log.Errorf("error creating mountpoint for %s: %s", serial, err)
		return
	}
	defer removeMountpoint(deviceMountpoint)

	log.Infof("mounting %s on %s", serial, deviceMountpoint)
	cmd := config.buildMountCommandForDevice(deviceMountpoint, serial)

	log.Debugf("launching adbfs: %s %s", cmd.Path, strings.Join(cmd.Args, " "))
	if err = cmd.Start(); err != nil {
		log.Errorln("error starting adbfs process:", err)
		return
	}

	log.Infof("device %s mounted with PID %d", serial, cmd.Process.Pid)

	if err = cmd.Wait(); err != nil {
		if err, ok := err.(*exec.ExitError); ok {
			log.Errorf("adbfs exited with %+v", err)
		} else {
			log.Errorf("lost connection with adbfs process:", err)
		}
		return
	}

	log.Infof("mount process for device %s stopped", serial)
}

func createMountpointForDevice(config *Config, serial string) (mountpoint string, err error) {
	adbClient := goadb.NewDeviceClient(cli.Config.ClientConfig(), goadb.DeviceWithSerial(serial))
	deviceInfo, err := adbClient.GetDeviceInfo()
	if err != nil {
		return
	}

	dirName := buildDirNameForDevice(deviceInfo)
	mountpoint = filepath.Join(config.mountRoot, dirName)

	if doesFileExist(mountpoint) {
		err = fmt.Errorf("directory exists: %s", serial, mountpoint)
		return
	}

	log.Debugf("creating %s with permissions %s", mountpoint, config.mountpointPerm)
	err = os.Mkdir(mountpoint, config.mountpointPerm)
	return
}

func removeMountpoint(path string) {
	log.Debugln("removing mountpoint", path)
	if err := os.Remove(path); err != nil {
		log.Errorf("error removing mountpoint %s: %s", path, err)
	} else {
		log.Debugln("mountpoint removed successfully.")
	}
}

func doesFileExist(path string) bool {
	_, err := os.Stat(path)
	return err == os.ErrNotExist
}

func buildDirNameForDevice(deviceInfo *goadb.DeviceInfo) string {
	rawName := fmt.Sprintf("%s-%s", deviceInfo.Model, deviceInfo.Serial)
	return dirNameCleanerRegexp.ReplaceAllLiteralString(rawName, "_")
}

func (c *Config) buildMountCommandForDevice(mountpoint, serial string) (cmd exec.Cmd) {
	// Copy the base command, don't mutate it.
	cmd = c.adbfsBaseCommand
	cmd.Args = append(cli.Config.AsArgs(),
		fmt.Sprintf("--device=%s", serial),
		fmt.Sprintf("--mountpoint=%s", mountpoint))
	return
}
<|endoftext|>


<commit_before>package audio

import (
	"testing"
	"time"

	"github.com/200sc/klangsynthese/font"
	"github.com/200sc/klangsynthese/synth"
	"github.com/stretchr/testify/assert"
)

func TestPosFilter(t *testing.T) {
	kla, err := synth.Int16.Sin()
	assert.Nil(t, err)
	x, y := new(float64), new(float64)
	a := New(DefFont, kla.(Data), x, y)
	x2 := 100.0
	y2 := 100.0
	DefFont.Filter(PosFilter(NewEars(&x2, &y2, 100, 300)))
	err = <-a.Play()
	time.Sleep(a.PlayLength())
	// Assert left ear hears audio
	x2 -= 200
	err = <-a.Play()
	time.Sleep(a.PlayLength())
	// Assert right ear hears audio
	y2 += 500
	err = <-a.Play()
	time.Sleep(a.PlayLength())
	// Assert nothing is heard
	*DefFont = *font.New()
	DefFont.Filter(PosFilter(NewEars(&x2, &y2, 100, 2000)))
	x2 -= 200
	err = <-a.Play()
	time.Sleep(a.PlayLength())
	// Assert right ear hears audio
	x2 += 1000
	err = <-a.Play()
	time.Sleep(a.PlayLength())
	// Assert left ear hears audio

	_, _ = kla.Filter(PosFilter(NewEars(&x2, &y2, 0, 0)))
	// assert.NotNil(t, err)
}
<commit_msg>Add error checks to posFilter_test.go<commit_after>package audio

import (
	"testing"
	"time"

	"github.com/200sc/klangsynthese/font"
	"github.com/200sc/klangsynthese/synth"
	"github.com/stretchr/testify/assert"
)

func TestPosFilter(t *testing.T) {
	kla, err := synth.Int16.Sin()
	assert.Nil(t, err)
	x, y := new(float64), new(float64)
	a := New(DefFont, kla.(Data), x, y)
	x2 := 100.0
	y2 := 100.0
	DefFont.Filter(PosFilter(NewEars(&x2, &y2, 100, 300)))
	err = <-a.Play()
	assert.NotNil(t, err)
	time.Sleep(a.PlayLength())
	// Assert left ear hears audio
	x2 -= 200
	err = <-a.Play()
	assert.NotNil(t, err)
	time.Sleep(a.PlayLength())
	// Assert right ear hears audio
	y2 += 500
	err = <-a.Play()
	assert.NotNil(t, err)
	time.Sleep(a.PlayLength())
	// Assert nothing is heard
	*DefFont = *font.New()
	DefFont.Filter(PosFilter(NewEars(&x2, &y2, 100, 2000)))
	x2 -= 200
	err = <-a.Play()
	assert.NotNil(t, err)
	time.Sleep(a.PlayLength())
	// Assert right ear hears audio
	x2 += 1000
	err = <-a.Play()
	assert.NotNil(t, err)
	time.Sleep(a.PlayLength())
	// Assert left ear hears audio

	_, _ = kla.Filter(PosFilter(NewEars(&x2, &y2, 0, 0)))
	// assert.NotNil(t, err)
}
<|endoftext|>