<commit_before>// Copyright (c) 2013-2014 by Michael Dvorkin. All Rights Reserved.
// Use of this source code is governed by a MIT-style license that can
// be found in the LICENSE file.

package donna

import (
	`fmt`
	`math/rand`
	`time`
)

// Returns row number in 0..7 range for the given square.
func row(square int) int {
	return square >> 3
}

// Returns column number in 0..7 range for the given square.
func col(square int) int {
	return square & 7
}

// Returns both row and column numbers for the given square.
func coordinate(square int) (int, int) {
	return row(square), col(square)
}

// Returns relative rank for the square in 0..7 range. For example E2 is rank 1
// for white and rank 6 for black.
func rank(square, color int) int {
	return row(square) ^ (color * 7)
}

// Returns 0..63 square number for the given row/column coordinate.
func square(row, column int) int {
	return (row << 3) + column
}

// Flips the square verically for white (ex. E2 becomes E7).
func flip(color, square int) int {
	if color == White {
		return square ^ 56
	}
	return square
}

// Returns a bitmask with light or dark squares set matching the color of the
// square.
func same(square int) Bitmask {
	if bit[square] & maskDark != 0 {
		return maskDark
	}
	return ^maskDark
}

// Returns true if the square resides between two other squares on the same line
// or diagonal, including the edge squares. For example, between(A1, H8, C3) is
// true.
func between(from, to, between int) bool {
	return (maskStraight[from][to] | maskDiagonal[from][to]).on(between)
}

// Returns distance between current and root node.
func ply() int {
	return node - rootNode
}

// Integer version of math/abs.
func abs(n int) int {
	if n < 0 {
		return -n
	}
	return n
}

func min(x, y int) int {
	if x < y {
		return x
	}
	return y
}

func max(x, y int) int {
	if x > y {
		return x
	}
	return y
}

func min64(x, y int64) int64 {
	if x < y {
		return x
	}
	return y
}

func max64(x, y int64) int64 {
	if x > y {
		return x
	}
	return y
}

// Formats time duration in milliseconds in human readable form: MM:SS.XXX
func ms(duration int64) string {
	mm := duration / 1000 / 60
	ss := duration / 1000 % 60
	xx := duration - mm * 1000 * 60 - ss * 1000
	return fmt.Sprintf(`%02d:%02d.%03ds`, mm, ss, xx)
}

// Returns, as an integer, a non-negative pseudo-random number
// in [0, limit) range. It panics if limit <= 0.
func Random(limit int) int {
	rand.Seed(time.Now().Unix())
	return rand.Intn(limit)
}

func C(color int) string {
	return [2]string{`white`, `black`}[color]
}

func Summary(metrics map[string]interface{}) {
	phase := metrics[`Phase`].(int)
	tally := metrics[`PST`].(Score)
	material := metrics[`Imbalance`].(Score)
	final := metrics[`Final`].(Score)
	units := float32(onePawn)

	fmt.Println()
	fmt.Printf("Metric              MidGame        |        EndGame        | Blended\n")
	fmt.Printf("                W      B     W-B   |    W      B     W-B   |  (%d)  \n", phase)
	fmt.Printf("-----------------------------------+-----------------------+--------\n")
	fmt.Printf("%-12s    -      -    %5.2f  |    -      -    %5.2f  >  %5.2f\n", `PST`,
		float32(tally.midgame)/units, float32(tally.endgame)/units, float32(tally.blended(phase))/units)
	fmt.Printf("%-12s    -      -    %5.2f  |    -      -    %5.2f  >  %5.2f\n", `Imbalance`,
		float32(material.midgame)/units, float32(material.endgame)/units, float32(material.blended(phase))/units)

	for _, tag := range([]string{`Tempo`, `Threats`, `Pawns`, `Passers`, `Mobility`, `+Pieces`, `-Knights`, `-Bishops`, `-Rooks`, `-Queens`, `+King`, `-Cover`, `-Safety`}) {
		white := metrics[tag].(Total).white
		black := metrics[tag].(Total).black

		var score Score
		score.add(white).subtract(black)

		if tag[0:1] == `+` {
			tag = tag[1:]
		} else if tag[0:1] == `-` {
			tag = `  ` + tag[1:]
		}

		fmt.Printf("%-12s  %5.2f  %5.2f  %5.2f  |  %5.2f  %5.2f  %5.2f  >  %5.2f\n", tag,
			float32(white.midgame)/units, float32(black.midgame)/units, float32(score.midgame)/units,
			float32(white.endgame)/units, float32(black.endgame)/units, float32(score.endgame)/units,
			float32(score.blended(phase))/units)
	}
	fmt.Printf("%-12s    -      -    %5.2f  |    -      -    %5.2f  >  %5.2f\n\n", `Final Score`,
		float32(final.midgame)/units, float32(final.endgame)/units, float32(final.blended(phase))/units)
}

// Logging wrapper around fmt.Printf() that could be turned on as needed. Typical
// usage is Log(); defer Log() in tests.
func Log(args ...interface{}) {
	switch len(args) {
	case 0:
		// Calling Log() with no arguments flips the logging setting.
		engine.log = !engine.log
		engine.fancy = !engine.fancy
	case 1:
		switch args[0].(type) {
		case bool:
			engine.log = args[0].(bool)
			engine.fancy = args[0].(bool)
		default:
			if engine.log {
				fmt.Println(args...)
			}
		}
	default:
		if engine.log {
			fmt.Printf(args[0].(string), args[1:]...)
		}
	}
}
<commit_msg>Display Center evaluation scores<commit_after>// Copyright (c) 2013-2014 by Michael Dvorkin. All Rights Reserved.
// Use of this source code is governed by a MIT-style license that can
// be found in the LICENSE file.

package donna

import (
	`fmt`
	`math/rand`
	`time`
)

// Returns row number in 0..7 range for the given square.
func row(square int) int {
	return square >> 3
}

// Returns column number in 0..7 range for the given square.
func col(square int) int {
	return square & 7
}

// Returns both row and column numbers for the given square.
func coordinate(square int) (int, int) {
	return row(square), col(square)
}

// Returns relative rank for the square in 0..7 range. For example E2 is rank 1
// for white and rank 6 for black.
func rank(square, color int) int {
	return row(square) ^ (color * 7)
}

// Returns 0..63 square number for the given row/column coordinate.
func square(row, column int) int {
	return (row << 3) + column
}

// Flips the square verically for white (ex. E2 becomes E7).
func flip(color, square int) int {
	if color == White {
		return square ^ 56
	}
	return square
}

// Returns a bitmask with light or dark squares set matching the color of the
// square.
func same(square int) Bitmask {
	if bit[square] & maskDark != 0 {
		return maskDark
	}
	return ^maskDark
}

// Returns true if the square resides between two other squares on the same line
// or diagonal, including the edge squares. For example, between(A1, H8, C3) is
// true.
func between(from, to, between int) bool {
	return (maskStraight[from][to] | maskDiagonal[from][to]).on(between)
}

// Returns distance between current and root node.
func ply() int {
	return node - rootNode
}

// Integer version of math/abs.
func abs(n int) int {
	if n < 0 {
		return -n
	}
	return n
}

func min(x, y int) int {
	if x < y {
		return x
	}
	return y
}

func max(x, y int) int {
	if x > y {
		return x
	}
	return y
}

func min64(x, y int64) int64 {
	if x < y {
		return x
	}
	return y
}

func max64(x, y int64) int64 {
	if x > y {
		return x
	}
	return y
}

// Formats time duration in milliseconds in human readable form: MM:SS.XXX
func ms(duration int64) string {
	mm := duration / 1000 / 60
	ss := duration / 1000 % 60
	xx := duration - mm * 1000 * 60 - ss * 1000
	return fmt.Sprintf(`%02d:%02d.%03ds`, mm, ss, xx)
}

// Returns, as an integer, a non-negative pseudo-random number
// in [0, limit) range. It panics if limit <= 0.
func Random(limit int) int {
	rand.Seed(time.Now().Unix())
	return rand.Intn(limit)
}

func C(color int) string {
	return [2]string{`white`, `black`}[color]
}

func Summary(metrics map[string]interface{}) {
	phase := metrics[`Phase`].(int)
	tally := metrics[`PST`].(Score)
	material := metrics[`Imbalance`].(Score)
	final := metrics[`Final`].(Score)
	units := float32(onePawn)

	fmt.Println()
	fmt.Printf("Metric              MidGame        |        EndGame        | Blended\n")
	fmt.Printf("                W      B     W-B   |    W      B     W-B   |  (%d)  \n", phase)
	fmt.Printf("-----------------------------------+-----------------------+--------\n")
	fmt.Printf("%-12s    -      -    %5.2f  |    -      -    %5.2f  >  %5.2f\n", `PST`,
		float32(tally.midgame)/units, float32(tally.endgame)/units, float32(tally.blended(phase))/units)
	fmt.Printf("%-12s    -      -    %5.2f  |    -      -    %5.2f  >  %5.2f\n", `Imbalance`,
		float32(material.midgame)/units, float32(material.endgame)/units, float32(material.blended(phase))/units)

	for _, tag := range([]string{`Tempo`, `Center`, `Threats`, `Pawns`, `Passers`, `Mobility`, `+Pieces`, `-Knights`, `-Bishops`, `-Rooks`, `-Queens`, `+King`, `-Cover`, `-Safety`}) {
		white := metrics[tag].(Total).white
		black := metrics[tag].(Total).black

		var score Score
		score.add(white).subtract(black)

		if tag[0:1] == `+` {
			tag = tag[1:]
		} else if tag[0:1] == `-` {
			tag = `  ` + tag[1:]
		}

		fmt.Printf("%-12s  %5.2f  %5.2f  %5.2f  |  %5.2f  %5.2f  %5.2f  >  %5.2f\n", tag,
			float32(white.midgame)/units, float32(black.midgame)/units, float32(score.midgame)/units,
			float32(white.endgame)/units, float32(black.endgame)/units, float32(score.endgame)/units,
			float32(score.blended(phase))/units)
	}
	fmt.Printf("%-12s    -      -    %5.2f  |    -      -    %5.2f  >  %5.2f\n\n", `Final Score`,
		float32(final.midgame)/units, float32(final.endgame)/units, float32(final.blended(phase))/units)
}

// Logging wrapper around fmt.Printf() that could be turned on as needed. Typical
// usage is Log(); defer Log() in tests.
func Log(args ...interface{}) {
	switch len(args) {
	case 0:
		// Calling Log() with no arguments flips the logging setting.
		engine.log = !engine.log
		engine.fancy = !engine.fancy
	case 1:
		switch args[0].(type) {
		case bool:
			engine.log = args[0].(bool)
			engine.fancy = args[0].(bool)
		default:
			if engine.log {
				fmt.Println(args...)
			}
		}
	default:
		if engine.log {
			fmt.Printf(args[0].(string), args[1:]...)
		}
	}
}
<|endoftext|>


<commit_before>/*
 * Run TCP/UDP service loop: Create listener for incoming connection
 * requests as a go-routine and call user-defined service handler as
 * a go-routine to handle client sessions.
 *
 * (c) 2010 Bernd Fix   >Y<
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package network

///////////////////////////////////////////////////////////////////////
// Import external declarations.

import (
	"os"
	"net"
	"log"
)

///////////////////////////////////////////////////////////////////////
// Public interfaces

/*
 * User-defined service handler: Handle TCP/UDP client sessions.
 * The interface defines four methods:
 * - Process (conn): Main handler routine for connection
 * - GetName(): Return service name (for logging output)
 * - CanHandle (protocol): Check if handler can process given
 *       network protocol (TCP or UDP on IPv4 or IPv6)
 * - IsAllowed (addr): Checkk if remote address is allowed to
 *       be served by the service handler.
 */
type Service interface {
	Process (conn net.Conn)				// main handler routine
	GetName() string					// get symbolic name of service
	CanHandle (protocol string) bool	// check network protocol
	IsAllowed (remote string) bool		// check remote address
}

///////////////////////////////////////////////////////////////////////
// Public methods

/*
 * Run TCP/UDP network service with user-defined session handler.
 * @param network string - network identifier (TCP/UDP on IPv4/v6)
 * @param addr string - address:port specification of service
 * @param hdlr Service - implementation of service interface
 */
func Run (network, addr string, hdlr Service) os.Error {

	// initialize control service
	service, err := net.Listen (network, addr)
	if err != nil {
		log.Println ("[" + hdlr.GetName() + "] service start-up failed: " + err.String())
		return err
	}

	// handle connection requests
	ch := make (chan net.Conn)
	go func() {
		for {
			// wait for connection request
			client, err := service.Accept()
			if err != nil {
				log.Println ("[" + hdlr.GetName() + "] Accept(): " + err.String())
				continue
			}
			// check for TCP connection from localhost
			remote  := client.RemoteAddr().String()
			protocol := client.RemoteAddr().Network()
			if !hdlr.CanHandle (protocol)  {
				log.Printf("[" + hdlr.GetName() + "] rejected non-TCP connection from %v\n", protocol)
				continue
			}
			if !hdlr.IsAllowed (remote)  {
				log.Printf("[" + hdlr.GetName() + "] rejected remote connection from %v\n", addr)
				continue
			}
			// connection accepted
			log.Printf("[" + hdlr.GetName() + "] accepted %v\n", addr)
			ch <- client
		}
	}()

	// handle incoming client connections
	for {
		go hdlr.Process (<-ch)
	}
	return nil
}

///////////////////////////////////////////////////////////////////////
//	Revision history:
///////////////////////////////////////////////////////////////////////
//
//	Revision 1.0  2010-11-18 23:17:06  brf
//  Initial revision.
//
<commit_msg>Close connection if session is rejected.<commit_after>/*
 * Run TCP/UDP service loop: Create listener for incoming connection
 * requests as a go-routine and call user-defined service handler as
 * a go-routine to handle client sessions.
 *
 * (c) 2010 Bernd Fix   >Y<
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package network

///////////////////////////////////////////////////////////////////////
// Import external declarations.

import (
	"os"
	"net"
	"log"
)

///////////////////////////////////////////////////////////////////////
// Public interfaces

/*
 * User-defined service handler: Handle TCP/UDP client sessions.
 * The interface defines four methods:
 * - Process (conn): Main handler routine for connection
 * - GetName(): Return service name (for logging output)
 * - CanHandle (protocol): Check if handler can process given
 *       network protocol (TCP or UDP on IPv4 or IPv6)
 * - IsAllowed (addr): Checkk if remote address is allowed to
 *       be served by the service handler.
 */
type Service interface {
	Process (conn net.Conn)				// main handler routine
	GetName() string					// get symbolic name of service
	CanHandle (protocol string) bool	// check network protocol
	IsAllowed (remote string) bool		// check remote address
}

///////////////////////////////////////////////////////////////////////
// Public methods

/*
 * Run TCP/UDP network service with user-defined session handler.
 * @param network string - network identifier (TCP/UDP on IPv4/v6)
 * @param addr string - address:port specification of service
 * @param hdlr Service - implementation of service interface
 */
func RunService (network, addr string, hdlr Service) os.Error {

	// initialize control service
	service, err := net.Listen (network, addr)
	if err != nil {
		log.Println ("[" + hdlr.GetName() + "] service start-up failed: " + err.String())
		return err
	}

	// handle connection requests
	ch := make (chan net.Conn)
	go func() {
		for {
			// wait for connection request
			client, err := service.Accept()
			if err != nil {
				log.Println ("[" + hdlr.GetName() + "] Accept(): " + err.String())
				continue
			}
			// check if connection is allowed:
			remote  := client.RemoteAddr().String()
			protocol := client.RemoteAddr().Network()
			// check for matching protocol
			if !hdlr.CanHandle (protocol)  {
				log.Printf("[" + hdlr.GetName() + "] rejected non-TCP connection from %v\n", protocol)
				client.Close()
				continue
			}
			// check for matching remote address
			if !hdlr.IsAllowed (remote)  {
				log.Printf("[" + hdlr.GetName() + "] rejected remote connection from %v\n", remote)
				client.Close()
				continue
			}
			// connection accepted
			log.Printf("[" + hdlr.GetName() + "] accepted %v\n", remote)
			ch <- client
		}
	}()

	// handle incoming client connections
	go func() {
		for {
			go hdlr.Process (<-ch)
		}
	}()

	// report success
	log.Println ("[" + hdlr.GetName() + "] service started...")
	return nil
}

///////////////////////////////////////////////////////////////////////
//	Revision history:
///////////////////////////////////////////////////////////////////////
//
//	Revision 1.0  2010-11-18 23:17:06  brf
//  Initial revision.
//
<|endoftext|>


<commit_before>package main

import (
	"io/ioutil"
	"strings"
)

// Convert and concatenate a []int8 slice to a string
// inspired by perterGo: https://groups.google.com/forum/#!topic/golang-nuts/Jel8Bb-YwX8
func int8SliceToString(char []int8) string {
	s := make([]string, len(char))
	for i := range s {
		s[i] = string(char[i])
	}
	return strings.Join(s, "")
}

// TODO maybe change this to convert exclusively [65]int8 array ?

// Read a text file and return the content without the EOF carriage return
// this utility may be used with files from ``/sys` or ``/proc` file systems
func readAndTrimFile(path string) string {
	data, err := ioutil.ReadFile(path)
	if err == nil {
		return strings.TrimRight(string(data), "\n") // Trimright remove the EOF carriage return
	}
	return ""
}

//TODO manage multiple line files ?
<commit_msg>Code refactoring<commit_after>package main

import (
	"io/ioutil"
	"strings"
)

// Convert and concatenate a []int8 slice to a string
// inspired by perterGo: https://groups.google.com/forum/#!topic/golang-nuts/Jel8Bb-YwX8
func int8SliceToString(char []int8) string {
	s := make([]string, len(char))
	for i := range s {
		s[i] = string(char[i])
	}
	return strings.Join(s, "")
}

// TODO maybe change this to convert exclusively [65]int8 array ?

// Read a text file and return the content without the EOF carriage return
// this utility may be used with files from ``/sys` or ``/proc` file systems
func readAndTrimFile(path string) string {
	ret := ""
	data, err := ioutil.ReadFile(path)
	if err == nil {
		ret = strings.TrimRight(string(data), "\n") // Trimright remove the EOF carriage return
	}
	return ret
}

//TODO manage multiple line files ?
<|endoftext|>


<commit_before>package apps

import (
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	. "github.com/onsi/gomega/gbytes"
	. "github.com/onsi/gomega/gexec"

	"github.com/cloudfoundry-incubator/cf-test-helpers/cf"
	"github.com/cloudfoundry-incubator/cf-test-helpers/generator"
	"github.com/cloudfoundry-incubator/cf-test-helpers/helpers"
	"github.com/cloudfoundry/cf-acceptance-tests/helpers/assets"
)

type AppUsageEvent struct {
	Entity struct {
		AppName       string `json:"app_name"`
		State         string `json:"state"`
		BuildpackName string `json:"buildpack_name"`
		BuildpackGuid string `json:"buildpack_guid"`
	} `json:"entity"`
}

type AppUsageEvents struct {
	Resources []AppUsageEvent `struct:"resources"`
}

func lastAppUsageEvent(appName string, state string) (bool, AppUsageEvent) {
	var response AppUsageEvents
	cf.AsUser(context.AdminUserContext(), DEFAULT_TIMEOUT, func() {
		cf.ApiRequest("GET", "/v2/app_usage_events?order-direction=desc&page=1", &response, DEFAULT_TIMEOUT)
	})

	for _, event := range response.Resources {
		if event.Entity.AppName == appName && event.Entity.State == state {
			return true, event
		}
	}

	return false, AppUsageEvent{}
}

var _ = Describe("Application Lifecycle", func() {
	var appName string

	BeforeEach(func() {
		appName = generator.RandomName()

		Expect(cf.Cf("push", appName, "-p", assets.NewAssets().Dora).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))
	})

	AfterEach(func() {
		Expect(cf.Cf("delete", appName, "-f").Wait(DEFAULT_TIMEOUT)).To(Exit(0))
	})

	Describe("pushing", func() {
		It("makes the app reachable via its bound route", func() {
			Eventually(func() string {
				return helpers.CurlAppRoot(appName)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hi, I'm Dora!"))
		})

		It("makes system environment variables available", func() {
			var envOutput string
			Eventually(func() string {
				envOutput = helpers.CurlApp(appName, "/env")
				return envOutput
			}, DEFAULT_TIMEOUT).Should(ContainSubstring(`"CF_INSTANCE_INDEX"=>"0"`))
			Expect(envOutput).To(MatchRegexp(`"CF_INSTANCE_IP"=>"[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+"`))
			Expect(envOutput).To(MatchRegexp(`"CF_INSTANCE_PORT"=>"[0-9]+"`))
			Expect(envOutput).To(MatchRegexp(`"CF_INSTANCE_ADDR"=>"[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+"`))
			Expect(envOutput).To(MatchRegexp(`"CF_INSTANCE_PORTS"=>"[{\\"external\\":[0-9]+,\\"internal\\":[0-9]+}]"`))
		})

		It("generates an app usage 'started' event", func() {
			found, _ := lastAppUsageEvent(appName, "STARTED")
			Expect(found).To(BeTrue())
		})

		It("generates an app usage 'buildpack_set' event", func() {
			found, matchingEvent := lastAppUsageEvent(appName, "BUILDPACK_SET")

			Expect(found).To(BeTrue())
			Expect(matchingEvent.Entity.BuildpackName).To(Equal("ruby_buildpack"))
			Expect(matchingEvent.Entity.BuildpackGuid).ToNot(BeZero())
		})
	})

	Describe("stopping", func() {
		BeforeEach(func() {
			Expect(cf.Cf("stop", appName).Wait(DEFAULT_TIMEOUT)).To(Exit(0))
		})

		It("makes the app unreachable", func() {
			Eventually(func() string {
				return helpers.CurlAppRoot(appName)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("404"))
		})

		It("generates an app usage 'stopped' event", func() {
			found, _ := lastAppUsageEvent(appName, "STOPPED")
			Expect(found).To(BeTrue())
		})

		Describe("and then starting", func() {
			BeforeEach(func() {
				Expect(cf.Cf("start", appName).Wait(DEFAULT_TIMEOUT)).To(Exit(0))
			})

			It("makes the app reachable again", func() {
				Eventually(func() string {
					return helpers.CurlAppRoot(appName)
				}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hi, I'm Dora!"))
			})
		})
	})

	Describe("updating", func() {
		It("is reflected through another push", func() {
			Eventually(func() string {
				return helpers.CurlAppRoot(appName)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hi, I'm Dora!"))

			Expect(cf.Cf("push", appName, "-p", assets.NewAssets().HelloWorld).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))

			Eventually(func() string {
				return helpers.CurlAppRoot(appName)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hello, world!"))
		})
	})

	Describe("deleting", func() {
		BeforeEach(func() {
			Expect(cf.Cf("delete", appName, "-f").Wait(DEFAULT_TIMEOUT)).To(Exit(0))
		})

		It("removes the application", func() {
			app := cf.Cf("app", appName).Wait(DEFAULT_TIMEOUT)
			Expect(app).To(Exit(1))
			Expect(app).To(Say("not found"))
		})

		It("makes the app unreachable", func() {
			Eventually(func() string {
				return helpers.CurlAppRoot(appName)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("404"))
		})

		It("generates an app usage 'stopped' event", func() {
			found, _ := lastAppUsageEvent(appName, "STOPPED")
			Expect(found).To(BeTrue())
		})
	})
})
<commit_msg>Add a CAT for context path routes<commit_after>package apps

import (
	"encoding/json"
	"fmt"
	"strings"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	. "github.com/onsi/gomega/gbytes"
	. "github.com/onsi/gomega/gexec"

	"github.com/cloudfoundry-incubator/cf-test-helpers/cf"
	"github.com/cloudfoundry-incubator/cf-test-helpers/generator"
	"github.com/cloudfoundry-incubator/cf-test-helpers/helpers"
	"github.com/cloudfoundry/cf-acceptance-tests/helpers/assets"
)

type AppUsageEvent struct {
	Entity struct {
		AppName       string `json:"app_name"`
		State         string `json:"state"`
		BuildpackName string `json:"buildpack_name"`
		BuildpackGuid string `json:"buildpack_guid"`
	} `json:"entity"`
}

type AppUsageEvents struct {
	Resources []AppUsageEvent `struct:"resources"`
}

func lastAppUsageEvent(appName string, state string) (bool, AppUsageEvent) {
	var response AppUsageEvents
	cf.AsUser(context.AdminUserContext(), DEFAULT_TIMEOUT, func() {
		cf.ApiRequest("GET", "/v2/app_usage_events?order-direction=desc&page=1", &response, DEFAULT_TIMEOUT)
	})

	for _, event := range response.Resources {
		if event.Entity.AppName == appName && event.Entity.State == state {
			return true, event
		}
	}

	return false, AppUsageEvent{}
}

var _ = Describe("Application Lifecycle", func() {
	var appName string

	BeforeEach(func() {
		appName = generator.RandomName()

		Expect(cf.Cf("push", appName, "-p", assets.NewAssets().Dora).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))
	})

	AfterEach(func() {
		Expect(cf.Cf("delete", appName, "-f").Wait(DEFAULT_TIMEOUT)).To(Exit(0))
	})

	Describe("pushing", func() {
		It("makes the app reachable via its bound route", func() {
			Eventually(func() string {
				return helpers.CurlAppRoot(appName)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hi, I'm Dora!"))
		})

		Describe("Context path", func() {
			var app2 string
			var path = "/imposter_dora"

			BeforeEach(func() {
				app2 = generator.RandomName()
				Expect(cf.Cf("push", app2, "-p", assets.NewAssets().HelloWorld).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))
			})

			AfterEach(func() {
				Expect(cf.Cf("delete", app2, "-f").Wait(DEFAULT_TIMEOUT)).To(Exit(0))
			})

			It("makes another app available via same host and domain, but different path", func() {
				getRoutePath := fmt.Sprintf("/v2/routes?q=host:%s", appName)
				routeBody := cf.Cf("curl", getRoutePath).Wait(DEFAULT_TIMEOUT).Out.Contents()
				var routeJSON struct {
					Resources []struct {
						Entity struct {
							SpaceGuid  string `json:"space_guid"`
							DomainGuid string `json:"domain_guid"`
						} `json:"entity"`
					} `json:"resources"`
				}
				json.Unmarshal([]byte(routeBody), &routeJSON)

				spaceGuid := routeJSON.Resources[0].Entity.SpaceGuid
				domainGuid := routeJSON.Resources[0].Entity.DomainGuid
				appGuid := cf.Cf("app", app2, "--guid").Wait(DEFAULT_TIMEOUT).Out.Contents()

				jsonBody := "{\"host\":\"" + appName + "\", \"path\":\"" + path + "\", \"domain_guid\":\"" + domainGuid + "\",\"space_guid\":\"" + spaceGuid + "\"}"
				routePostResponseBody := cf.Cf("curl", "/v2/routes", "-X", "POST", "-d", jsonBody).Wait(CF_PUSH_TIMEOUT).Out.Contents()

				var routeResponseJSON struct {
					Metadata struct {
						Guid string `json:"guid"`
					} `json:"metadata"`
				}
				json.Unmarshal([]byte(routePostResponseBody), &routeResponseJSON)
				routeGuid := routeResponseJSON.Metadata.Guid

				Expect(cf.Cf("curl", "/v2/apps/"+strings.TrimSpace(string(appGuid))+"/routes/"+string(routeGuid), "-X", "PUT").Wait(CF_PUSH_TIMEOUT)).To(Exit(0))

				Eventually(func() string {
					return helpers.CurlAppRoot(appName)
				}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hi, I'm Dora!"))

				Eventually(func() string {
					return helpers.CurlApp(appName, path)
				}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hello, world!"))
			})
		})

		It("makes system environment variables available", func() {
			var envOutput string
			Eventually(func() string {
				envOutput = helpers.CurlApp(appName, "/env")
				return envOutput
			}, DEFAULT_TIMEOUT).Should(ContainSubstring(`"CF_INSTANCE_INDEX"=>"0"`))
			Expect(envOutput).To(MatchRegexp(`"CF_INSTANCE_IP"=>"[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+"`))
			Expect(envOutput).To(MatchRegexp(`"CF_INSTANCE_PORT"=>"[0-9]+"`))
			Expect(envOutput).To(MatchRegexp(`"CF_INSTANCE_ADDR"=>"[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+"`))
			Expect(envOutput).To(MatchRegexp(`"CF_INSTANCE_PORTS"=>"[{\\"external\\":[0-9]+,\\"internal\\":[0-9]+}]"`))
		})

		It("generates an app usage 'started' event", func() {
			found, _ := lastAppUsageEvent(appName, "STARTED")
			Expect(found).To(BeTrue())
		})

		It("generates an app usage 'buildpack_set' event", func() {
			found, matchingEvent := lastAppUsageEvent(appName, "BUILDPACK_SET")

			Expect(found).To(BeTrue())
			Expect(matchingEvent.Entity.BuildpackName).To(Equal("ruby_buildpack"))
			Expect(matchingEvent.Entity.BuildpackGuid).ToNot(BeZero())
		})
	})

	Describe("stopping", func() {
		BeforeEach(func() {
			Expect(cf.Cf("stop", appName).Wait(DEFAULT_TIMEOUT)).To(Exit(0))
		})

		It("makes the app unreachable", func() {
			Eventually(func() string {
				return helpers.CurlAppRoot(appName)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("404"))
		})

		It("generates an app usage 'stopped' event", func() {
			found, _ := lastAppUsageEvent(appName, "STOPPED")
			Expect(found).To(BeTrue())
		})

		Describe("and then starting", func() {
			BeforeEach(func() {
				Expect(cf.Cf("start", appName).Wait(DEFAULT_TIMEOUT)).To(Exit(0))
			})

			It("makes the app reachable again", func() {
				Eventually(func() string {
					return helpers.CurlAppRoot(appName)
				}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hi, I'm Dora!"))
			})
		})
	})

	Describe("updating", func() {
		It("is reflected through another push", func() {
			Eventually(func() string {
				return helpers.CurlAppRoot(appName)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hi, I'm Dora!"))

			Expect(cf.Cf("push", appName, "-p", assets.NewAssets().HelloWorld).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))

			Eventually(func() string {
				return helpers.CurlAppRoot(appName)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hello, world!"))
		})
	})

	Describe("deleting", func() {
		BeforeEach(func() {
			Expect(cf.Cf("delete", appName, "-f").Wait(DEFAULT_TIMEOUT)).To(Exit(0))
		})

		It("removes the application", func() {
			app := cf.Cf("app", appName).Wait(DEFAULT_TIMEOUT)
			Expect(app).To(Exit(1))
			Expect(app).To(Say("not found"))
		})

		It("makes the app unreachable", func() {
			Eventually(func() string {
				return helpers.CurlAppRoot(appName)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("404"))
		})

		It("generates an app usage 'stopped' event", func() {
			found, _ := lastAppUsageEvent(appName, "STOPPED")
			Expect(found).To(BeTrue())
		})
	})
})
<|endoftext|>


<commit_before>// Copyright 2013 The lime Authors.
// Use of this source code is governed by a 2-clause
// BSD-style license that can be found in the LICENSE file.

package util

import (
	"strings"
)

// Naive algorithm from http://en.wikipedia.org/wiki/Longest_common_subsequence_problem
func mDiff(av, bv []string) (ret []string) {
	matrix := make([]int, (len(av)+1)*(len(bv)+1))
	pitch := (len(bv) + 1)
	for i, a := range av {
		mp := (i+1)*pitch + 1

		for _, b := range bv {
			if a == b {
				matrix[mp] = matrix[mp-1-pitch] + 1
			} else if matrix[mp-1] > matrix[mp-pitch] {
				matrix[mp] = matrix[mp-1]
			} else {
				matrix[mp] = matrix[mp-pitch]
			}
			mp++
		}
	}
	var inner func(i, j int, context int)
	inner = func(i, j int, context int) {
		if i > 0 && j > 0 && av[i-1] == bv[j-1] {
			i--
			j--
			inner(i, j, context-1)
			if context > 0 {
				ret = append(ret, "  "+bv[j])
			}
		} else if j > 0 && (i == 0 || matrix[i*pitch+j-1] >= matrix[(i-1)*pitch+j]) {
			inner(i, j-1, 3)
			ret = append(ret, "+ "+bv[j-1])
		} else if i > 0 && (j == 0 || matrix[i*pitch+j-1] < matrix[(i-1)*pitch+j]) {
			inner(i-1, j, 3)
			ret = append(ret, "- "+av[i-1])
		}
	}
	inner(len(av), len(bv), 0)
	return
}

func Diff(a, b string) string {
	if a == b {
		return ""
	}
	a = strings.Replace(a, "\r", "", -1)
	b = strings.Replace(b, "\r", "", -1)
	if a == b {
		return ""
	}
	return strings.Join(mDiff(strings.Split(a, "\n"), strings.Split(b, "\n")), "\n")
}
<commit_msg>Only replace \r if it's followed by \n when creating a diff.<commit_after>// Copyright 2013 The lime Authors.
// Use of this source code is governed by a 2-clause
// BSD-style license that can be found in the LICENSE file.

package util

import (
	"strings"
)

// Naive algorithm from http://en.wikipedia.org/wiki/Longest_common_subsequence_problem
func mDiff(av, bv []string) (ret []string) {
	matrix := make([]int, (len(av)+1)*(len(bv)+1))
	pitch := (len(bv) + 1)
	for i, a := range av {
		mp := (i+1)*pitch + 1

		for _, b := range bv {
			if a == b {
				matrix[mp] = matrix[mp-1-pitch] + 1
			} else if matrix[mp-1] > matrix[mp-pitch] {
				matrix[mp] = matrix[mp-1]
			} else {
				matrix[mp] = matrix[mp-pitch]
			}
			mp++
		}
	}
	var inner func(i, j int, context int)
	inner = func(i, j int, context int) {
		if i > 0 && j > 0 && av[i-1] == bv[j-1] {
			i--
			j--
			inner(i, j, context-1)
			if context > 0 {
				ret = append(ret, "  "+bv[j])
			}
		} else if j > 0 && (i == 0 || matrix[i*pitch+j-1] >= matrix[(i-1)*pitch+j]) {
			inner(i, j-1, 3)
			ret = append(ret, "+ "+bv[j-1])
		} else if i > 0 && (j == 0 || matrix[i*pitch+j-1] < matrix[(i-1)*pitch+j]) {
			inner(i-1, j, 3)
			ret = append(ret, "- "+av[i-1])
		}
	}
	inner(len(av), len(bv), 0)
	return
}

func Diff(a, b string) string {
	if a == b {
		return ""
	}
	a = strings.Replace(a, "\r\n", "\n", -1)
	b = strings.Replace(b, "\r\n", "\n", -1)
	if a == b {
		return ""
	}
	return strings.Join(mDiff(strings.Split(a, "\n"), strings.Split(b, "\n")), "\n")
}
<|endoftext|>


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

package cli

import (
	"context"
	"flag"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"reflect"
	"strings"

	"github.com/GoogleCloudPlatform/docker-credential-gcr/config"
	"github.com/GoogleCloudPlatform/docker-credential-gcr/store"
	"github.com/GoogleCloudPlatform/docker-credential-gcr/util"
	cliconfig "github.com/docker/cli/cli/config"
	"github.com/docker/cli/cli/config/configfile"
	"github.com/docker/docker/api/types"
	"github.com/google/subcommands"
)

type dockerConfigCmd struct {
	cmd
	// overwrite any previously configured credential store and/or credentials
	overwrite bool
}

// see https://github.com/docker/docker/blob/master/cliconfig/credentials/native_store.go
const credHelperPrefix = "docker-credential-"

// NewDockerConfigSubcommand returns a subcommands.Command which configures
// the docker client to use this credential helper
func NewDockerConfigSubcommand() subcommands.Command {
	return &dockerConfigCmd{
		cmd{
			name:     "configure-docker",
			synopsis: fmt.Sprintf("configures the Docker client to use %s", os.Args[0]),
		},
		false,
	}
}

func (c *dockerConfigCmd) SetFlags(fs *flag.FlagSet) {
	fs.BoolVar(&c.overwrite, "overwrite", false, "overwrite any previously configured credential store and/or credentials")
}

func (c *dockerConfigCmd) Execute(context.Context, *flag.FlagSet, ...interface{}) subcommands.ExitStatus {
	binaryName := filepath.Base(os.Args[0])
	if !strings.HasPrefix(binaryName, credHelperPrefix) {
		printErrorln("Binary name must be prefixed with '%s': %s", credHelperPrefix, binaryName)
		return subcommands.ExitFailure
	}

	// the Docker client can only use binaries on the $PATH
	if _, err := exec.LookPath(binaryName); err != nil {
		printErrorln("'%s' must exist on your PATH", binaryName)
		return subcommands.ExitFailure
	}

	dockerConfig, err := cliconfig.Load("")
	if err != nil {
		printErrorln("Unable to load docker config: %v", err)
		return subcommands.ExitFailure
	}

	// 'credsStore' and 'credHelpers' take the suffix of the credential helper
	// binary.
	credHelperSuffix := binaryName[len(credHelperPrefix):]

	major, minor, _, _, err := util.DockerClientVersion()
	if err != nil {
		fmt.Printf("WARNING: Unable to determine Docker version: %v\n", err)
		fmt.Printf("Configuring %s as a registry-specific helper. This is only supported by Docker client versions 1.13+\n", binaryName)
		return setConfig(dockerConfig, credHelperSuffix)
	} else if credHelpersSupported(major, minor) {
		// If we can act as a registry-specific credential helper, do so...
		return setConfig(dockerConfig, credHelperSuffix)
	} else if credsStoreSupported(major, minor) {
		// Else, attempt to act as the cred store...
		return c.setLegacyConfig(dockerConfig, credHelperSuffix)
	}

	// Netiher cred helper nor cred store is supported by the detected docker
	// version.
	fmt.Fprintln(os.Stderr, "ERROR: Docker client version 1.10+ required")
	return subcommands.ExitFailure
}

// credHelpersSupported returns true if the installed version of Docker supports
// credential helpers (1.13+), error if Docker is not installed or there was an
// error determining the version.
func credHelpersSupported(majorVersion, minorVersion int) bool {
	return majorVersion >= 17 || (majorVersion == 1 && minorVersion >= 13)
}

// credsStoreSupported returns true if the installed version of Docker supports
// credential stores (1.11+), error if Docker is not installed or there was an
// error determining the version.
func credsStoreSupported(majorVersion, minorVersion int) bool {
	return majorVersion >= 17 || (majorVersion == 1 && minorVersion >= 11)
}

// Configure Docker to use the credential helper for GCR's registries only.
// Defining additional 'auths' entries is unnecessary in versions which
// support registry-specific credential helpers.
func setConfig(dockerConfig *configfile.ConfigFile, helperSuffix string) subcommands.ExitStatus {
	// We always overwrite since there's no way that we can accidentally
	// disable other credentials as a registry-specific credential helper.
	if dockerConfig.CredentialHelpers == nil {
		dockerConfig.CredentialHelpers = map[string]string{}
	}

	for registry := range config.DefaultGCRRegistries {
		dockerConfig.CredentialHelpers[registry] = helperSuffix
	}

	if err := dockerConfig.Save(); err != nil {
		printErrorln("Unable to save docker config: %v", err)
		return subcommands.ExitFailure
	}

	fmt.Printf("%s configured to use this credential helper for GCR registries\n", dockerConfig.Filename)
	return subcommands.ExitSuccess
}

// Configure Docker to use the credential helper as the default credential
// store. Also add 'auths' entries to the config to support versions where the
// docker config was the source of truth for the set of stored credentials.
func (c *dockerConfigCmd) setLegacyConfig(dockerConfig *configfile.ConfigFile, helperSuffix string) subcommands.ExitStatus {
	// Only proceed if the creds store is empty or we're allowed to overwrite.
	// Replacing a cred store effectively makes any previously stored
	// credentials unreachable.
	otherCredStoreConfigured := dockerConfig.CredentialsStore != helperSuffix && dockerConfig.CredentialsStore != ""
	dcgcrConfiguredAsCredStore := dockerConfig.CredentialsStore == helperSuffix
	credentialsStored := len(dockerConfig.AuthConfigs) > 0
	if otherCredStoreConfigured && !c.overwrite {
		// If another credential store is configured, demand explicit
		// overwrite permissions.
		printErrorln("Docker is currently configured to use '%s%s' as its credential store. Please retry with --overwrite. This will render any previously store credentials unaccessible.", credHelperPrefix, dockerConfig.CredentialsStore)
		return subcommands.ExitFailure
	} else if credentialsStored && !dcgcrConfiguredAsCredStore && !c.overwrite {
		// If there are credentials stored somewhere other than this credential
		// helper, demand explicit overwrite permissions.
		printErrorln("%d credentials are currently stored which would be overwritten. Retry with --overwrite.", len(dockerConfig.AuthConfigs))
		return subcommands.ExitFailure
	}

	// Populate the AuthConfigs portion of the config.
	// This allows 'docker build' work on Docker client versions 1.11 and 1.12,
	// where AuthConfigs was
	s, err := store.DefaultGCRCredStore()
	if err != nil {
		printErrorln("Unable to read credentialStore: %v", err)
	}
	authsModified := setAuthConfigs(dockerConfig, s)

	// Optimization. Don't modify the dockerConfig if we're already fully configured.
	if authsModified || dockerConfig.CredentialsStore != helperSuffix {
		// Overwrite the existing set of AuthConfigs since they aren't visible anymore, anyway.
		dockerConfig.CredentialsStore = helperSuffix
		if err = dockerConfig.Save(); err != nil {
			printErrorln("Unable to save docker config: %v", err)
			return subcommands.ExitFailure
		}
	}

	fmt.Printf("%s successfully configured\n", dockerConfig.Filename)
	if c.overwrite {
		fmt.Println("Any previously stored credentials have been overwritten.")
	}
	return subcommands.ExitSuccess
}

// Ensures that the AuthConfigs in the given ConfigFile are exactly the set
// of config.DefaultGCRRegistries with the https scheme plus any 3p creds
// we have stored.
// Returns true if the ConfigFile was modified, false otherwise.
func setAuthConfigs(dockerConfig *configfile.ConfigFile, s store.GCRCredStore) bool {
	newAuthconfigs := make(map[string]types.AuthConfig)
	for registry := range config.DefaultGCRRegistries {
		// 'auths' members take a scheme
		registry = "https://" + registry
		newAuthconfigs[registry] = types.AuthConfig{}
	}

	creds, err := s.AllThirdPartyCreds()
	// Only add 3p creds if we can retrieve them, but FUBAR cred store is OK
	if err == nil {
		for registry := range creds {
			newAuthconfigs[registry] = types.AuthConfig{}
		}
	}

	if !reflect.DeepEqual(newAuthconfigs, dockerConfig.AuthConfigs) {
		dockerConfig.AuthConfigs = newAuthconfigs
		return true
	}

	return false
}

func printErrorln(fmtString string, v ...interface{}) {
	fmt.Fprintf(os.Stderr, "ERROR: "+fmtString+"\n", v...)
}
<commit_msg>clarify help string when the Docker client version cannot be determined (#45)<commit_after>// Copyright 2016 Google, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cli

import (
	"context"
	"flag"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"reflect"
	"strings"

	"github.com/GoogleCloudPlatform/docker-credential-gcr/config"
	"github.com/GoogleCloudPlatform/docker-credential-gcr/store"
	"github.com/GoogleCloudPlatform/docker-credential-gcr/util"
	cliconfig "github.com/docker/cli/cli/config"
	"github.com/docker/cli/cli/config/configfile"
	"github.com/docker/docker/api/types"
	"github.com/google/subcommands"
)

type dockerConfigCmd struct {
	cmd
	// overwrite any previously configured credential store and/or credentials
	overwrite bool
}

// see https://github.com/docker/docker/blob/master/cliconfig/credentials/native_store.go
const credHelperPrefix = "docker-credential-"

// NewDockerConfigSubcommand returns a subcommands.Command which configures
// the docker client to use this credential helper
func NewDockerConfigSubcommand() subcommands.Command {
	return &dockerConfigCmd{
		cmd{
			name:     "configure-docker",
			synopsis: fmt.Sprintf("configures the Docker client to use %s", os.Args[0]),
		},
		false,
	}
}

func (c *dockerConfigCmd) SetFlags(fs *flag.FlagSet) {
	fs.BoolVar(&c.overwrite, "overwrite", false, "overwrite any previously configured credential store and/or credentials")
}

func (c *dockerConfigCmd) Execute(context.Context, *flag.FlagSet, ...interface{}) subcommands.ExitStatus {
	binaryName := filepath.Base(os.Args[0])
	if !strings.HasPrefix(binaryName, credHelperPrefix) {
		printErrorln("Binary name must be prefixed with '%s': %s", credHelperPrefix, binaryName)
		return subcommands.ExitFailure
	}

	// the Docker client can only use binaries on the $PATH
	if _, err := exec.LookPath(binaryName); err != nil {
		printErrorln("'%s' must exist on your PATH", binaryName)
		return subcommands.ExitFailure
	}

	dockerConfig, err := cliconfig.Load("")
	if err != nil {
		printErrorln("Unable to load docker config: %v", err)
		return subcommands.ExitFailure
	}

	// 'credsStore' and 'credHelpers' take the suffix of the credential helper
	// binary.
	credHelperSuffix := binaryName[len(credHelperPrefix):]

	major, minor, _, _, err := util.DockerClientVersion()
	if err != nil {
		fmt.Printf("WARNING: Unable to execute `docker version`: %v\n", err)
		fmt.Println("This is expected if `docker` is not installed, or if `dockerd` cannot be reached...")
		fmt.Printf("Configuring %s as a registry-specific credential helper. This is only supported by Docker client versions 1.13+\n", binaryName)
		return setConfig(dockerConfig, credHelperSuffix)
	} else if credHelpersSupported(major, minor) {
		// If we can act as a registry-specific credential helper, do so...
		return setConfig(dockerConfig, credHelperSuffix)
	} else if credsStoreSupported(major, minor) {
		// Else, attempt to act as the cred store...
		return c.setLegacyConfig(dockerConfig, credHelperSuffix)
	}

	// Netiher cred helper nor cred store is supported by the detected docker
	// version.
	fmt.Fprintln(os.Stderr, "ERROR: Docker client version 1.10+ required")
	return subcommands.ExitFailure
}

// credHelpersSupported returns true if the installed version of Docker supports
// credential helpers (1.13+), error if Docker is not installed or there was an
// error determining the version.
func credHelpersSupported(majorVersion, minorVersion int) bool {
	return majorVersion >= 17 || (majorVersion == 1 && minorVersion >= 13)
}

// credsStoreSupported returns true if the installed version of Docker supports
// credential stores (1.11+), error if Docker is not installed or there was an
// error determining the version.
func credsStoreSupported(majorVersion, minorVersion int) bool {
	return majorVersion >= 17 || (majorVersion == 1 && minorVersion >= 11)
}

// Configure Docker to use the credential helper for GCR's registries only.
// Defining additional 'auths' entries is unnecessary in versions which
// support registry-specific credential helpers.
func setConfig(dockerConfig *configfile.ConfigFile, helperSuffix string) subcommands.ExitStatus {
	// We always overwrite since there's no way that we can accidentally
	// disable other credentials as a registry-specific credential helper.
	if dockerConfig.CredentialHelpers == nil {
		dockerConfig.CredentialHelpers = map[string]string{}
	}

	for registry := range config.DefaultGCRRegistries {
		dockerConfig.CredentialHelpers[registry] = helperSuffix
	}

	if err := dockerConfig.Save(); err != nil {
		printErrorln("Unable to save docker config: %v", err)
		return subcommands.ExitFailure
	}

	fmt.Printf("%s configured to use this credential helper for GCR registries\n", dockerConfig.Filename)
	return subcommands.ExitSuccess
}

// Configure Docker to use the credential helper as the default credential
// store. Also add 'auths' entries to the config to support versions where the
// docker config was the source of truth for the set of stored credentials.
func (c *dockerConfigCmd) setLegacyConfig(dockerConfig *configfile.ConfigFile, helperSuffix string) subcommands.ExitStatus {
	// Only proceed if the creds store is empty or we're allowed to overwrite.
	// Replacing a cred store effectively makes any previously stored
	// credentials unreachable.
	otherCredStoreConfigured := dockerConfig.CredentialsStore != helperSuffix && dockerConfig.CredentialsStore != ""
	dcgcrConfiguredAsCredStore := dockerConfig.CredentialsStore == helperSuffix
	credentialsStored := len(dockerConfig.AuthConfigs) > 0
	if otherCredStoreConfigured && !c.overwrite {
		// If another credential store is configured, demand explicit
		// overwrite permissions.
		printErrorln("Docker is currently configured to use '%s%s' as its credential store. Please retry with --overwrite. This will render any previously store credentials unaccessible.", credHelperPrefix, dockerConfig.CredentialsStore)
		return subcommands.ExitFailure
	} else if credentialsStored && !dcgcrConfiguredAsCredStore && !c.overwrite {
		// If there are credentials stored somewhere other than this credential
		// helper, demand explicit overwrite permissions.
		printErrorln("%d credentials are currently stored which would be overwritten. Retry with --overwrite.", len(dockerConfig.AuthConfigs))
		return subcommands.ExitFailure
	}

	// Populate the AuthConfigs portion of the config.
	// This allows 'docker build' work on Docker client versions 1.11 and 1.12,
	// where AuthConfigs was
	s, err := store.DefaultGCRCredStore()
	if err != nil {
		printErrorln("Unable to read credentialStore: %v", err)
	}
	authsModified := setAuthConfigs(dockerConfig, s)

	// Optimization. Don't modify the dockerConfig if we're already fully configured.
	if authsModified || dockerConfig.CredentialsStore != helperSuffix {
		// Overwrite the existing set of AuthConfigs since they aren't visible anymore, anyway.
		dockerConfig.CredentialsStore = helperSuffix
		if err = dockerConfig.Save(); err != nil {
			printErrorln("Unable to save docker config: %v", err)
			return subcommands.ExitFailure
		}
	}

	fmt.Printf("%s successfully configured\n", dockerConfig.Filename)
	if c.overwrite {
		fmt.Println("Any previously stored credentials have been overwritten.")
	}
	return subcommands.ExitSuccess
}

// Ensures that the AuthConfigs in the given ConfigFile are exactly the set
// of config.DefaultGCRRegistries with the https scheme plus any 3p creds
// we have stored.
// Returns true if the ConfigFile was modified, false otherwise.
func setAuthConfigs(dockerConfig *configfile.ConfigFile, s store.GCRCredStore) bool {
	newAuthconfigs := make(map[string]types.AuthConfig)
	for registry := range config.DefaultGCRRegistries {
		// 'auths' members take a scheme
		registry = "https://" + registry
		newAuthconfigs[registry] = types.AuthConfig{}
	}

	creds, err := s.AllThirdPartyCreds()
	// Only add 3p creds if we can retrieve them, but FUBAR cred store is OK
	if err == nil {
		for registry := range creds {
			newAuthconfigs[registry] = types.AuthConfig{}
		}
	}

	if !reflect.DeepEqual(newAuthconfigs, dockerConfig.AuthConfigs) {
		dockerConfig.AuthConfigs = newAuthconfigs
		return true
	}

	return false
}

func printErrorln(fmtString string, v ...interface{}) {
	fmt.Fprintf(os.Stderr, "ERROR: "+fmtString+"\n", v...)
}
<|endoftext|>


<commit_before>package client

import (
	"testing"
	"time"
)

func TestHandlerSet(t *testing.T) {
	// A Conn is needed here because the previous behaviour of passing nil to
	// hset.dispatch causes a nil pointer dereference with panic recovery.
	c, s := setUp(t)
	defer s.tearDown()

	hs := handlerSet()
	if len(hs.set) != 0 {
		t.Errorf("New set contains things!")
	}

	callcount := 0
	f := func(_ *Conn, _ *Line) {
		callcount++
	}

	// Add one
	hn1 := hs.add("ONE", HandlerFunc(f)).(*hNode)
	hl, ok := hs.set["one"]
	if len(hs.set) != 1 || !ok {
		t.Errorf("Set doesn't contain 'one' list after add().")
	}
	if hn1.set != hs || hn1.event != "one" || hn1.prev != nil || hn1.next != nil {
		t.Errorf("First node for 'one' not created correctly")
	}
	if hl.start != hn1 || hl.end != hn1 {
		t.Errorf("Node not added to empty 'one' list correctly.")
	}

	// Add another one...
	hn2 := hs.add("one", HandlerFunc(f)).(*hNode)
	if len(hs.set) != 1 {
		t.Errorf("Set contains more than 'one' list after add().")
	}
	if hn2.set != hs || hn2.event != "one" {
		t.Errorf("Second node for 'one' not created correctly")
	}
	if hn1.prev != nil || hn1.next != hn2 || hn2.prev != hn1 || hn2.next != nil {
		t.Errorf("Nodes for 'one' not linked correctly.")
	}
	if hl.start != hn1 || hl.end != hn2 {
		t.Errorf("Node not appended to 'one' list correctly.")
	}

	// Add a third one!
	hn3 := hs.add("one", HandlerFunc(f)).(*hNode)
	if len(hs.set) != 1 {
		t.Errorf("Set contains more than 'one' list after add().")
	}
	if hn3.set != hs || hn3.event != "one" {
		t.Errorf("Third node for 'one' not created correctly")
	}
	if hn1.prev != nil || hn1.next != hn2 ||
		hn2.prev != hn1 || hn2.next != hn3 ||
		hn3.prev != hn2 || hn3.next != nil {
		t.Errorf("Nodes for 'one' not linked correctly.")
	}
	if hl.start != hn1 || hl.end != hn3 {
		t.Errorf("Node not appended to 'one' list correctly.")
	}

	// And finally a fourth one!
	hn4 := hs.add("one", HandlerFunc(f)).(*hNode)
	if len(hs.set) != 1 {
		t.Errorf("Set contains more than 'one' list after add().")
	}
	if hn4.set != hs || hn4.event != "one" {
		t.Errorf("Fourth node for 'one' not created correctly.")
	}
	if hn1.prev != nil || hn1.next != hn2 ||
		hn2.prev != hn1 || hn2.next != hn3 ||
		hn3.prev != hn2 || hn3.next != hn4 ||
		hn4.prev != hn3 || hn4.next != nil {
		t.Errorf("Nodes for 'one' not linked correctly.")
	}
	if hl.start != hn1 || hl.end != hn4 {
		t.Errorf("Node not appended to 'one' list correctly.")
	}

	// Dispatch should result in 4 additions.
	if callcount != 0 {
		t.Errorf("Something incremented call count before we were expecting it.")
	}
	hs.dispatch(c, &Line{Cmd: "One"})
	<-time.After(time.Millisecond)
	if callcount != 4 {
		t.Errorf("Our handler wasn't called four times :-(")
	}

	// Remove node 3.
	hn3.Remove()
	if len(hs.set) != 1 {
		t.Errorf("Set list count changed after remove().")
	}
	if hn3.set != nil || hn3.prev != nil || hn3.next != nil {
		t.Errorf("Third node for 'one' not removed correctly.")
	}
	if hn1.prev != nil || hn1.next != hn2 ||
		hn2.prev != hn1 || hn2.next != hn4 ||
		hn4.prev != hn2 || hn4.next != nil {
		t.Errorf("Third node for 'one' not unlinked correctly.")
	}
	if hl.start != hn1 || hl.end != hn4 {
		t.Errorf("Third node for 'one' changed list pointers.")
	}

	// Dispatch should result in 3 additions.
	hs.dispatch(c, &Line{Cmd: "One"})
	<-time.After(time.Millisecond)
	if callcount != 7 {
		t.Errorf("Our handler wasn't called three times :-(")
	}

	// Remove node 1.
	hs.remove(hn1)
	if len(hs.set) != 1 {
		t.Errorf("Set list count changed after remove().")
	}
	if hn1.set != nil || hn1.prev != nil || hn1.next != nil {
		t.Errorf("First node for 'one' not removed correctly.")
	}
	if hn2.prev != nil || hn2.next != hn4 || hn4.prev != hn2 || hn4.next != nil {
		t.Errorf("First node for 'one' not unlinked correctly.")
	}
	if hl.start != hn2 || hl.end != hn4 {
		t.Errorf("First node for 'one' didn't change list pointers.")
	}

	// Dispatch should result in 2 additions.
	hs.dispatch(c, &Line{Cmd: "One"})
	<-time.After(time.Millisecond)
	if callcount != 9 {
		t.Errorf("Our handler wasn't called two times :-(")
	}

	// Remove node 4.
	hn4.Remove()
	if len(hs.set) != 1 {
		t.Errorf("Set list count changed after remove().")
	}
	if hn4.set != nil || hn4.prev != nil || hn4.next != nil {
		t.Errorf("Fourth node for 'one' not removed correctly.")
	}
	if hn2.prev != nil || hn2.next != nil {
		t.Errorf("Fourth node for 'one' not unlinked correctly.")
	}
	if hl.start != hn2 || hl.end != hn2 {
		t.Errorf("Fourth node for 'one' didn't change list pointers.")
	}

	// Dispatch should result in 1 addition.
	hs.dispatch(c, &Line{Cmd: "One"})
	<-time.After(time.Millisecond)
	if callcount != 10 {
		t.Errorf("Our handler wasn't called once :-(")
	}

	// Remove node 2.
	hs.remove(hn2)
	if len(hs.set) != 0 {
		t.Errorf("Removing last node in 'one' didn't remove list.")
	}
	if hn2.set != nil || hn2.prev != nil || hn2.next != nil {
		t.Errorf("Second node for 'one' not removed correctly.")
	}
	if hl.start != nil || hl.end != nil {
		t.Errorf("Second node for 'one' didn't change list pointers.")
	}

	// Dispatch should result in NO additions.
	hs.dispatch(c, &Line{Cmd: "One"})
	<-time.After(time.Millisecond)
	if callcount != 10 {
		t.Errorf("Our handler was called?")
	}
}

func TestPanicRecovery(t *testing.T) {
	c, s := setUp(t)
	defer s.tearDown()

	recovered := callCheck(t)
	c.cfg.Recover = func(conn *Conn, line *Line) {
		if err, ok := recover().(string); ok && err == "panic!" {
			recovered.call()
		}
	}
	c.HandleFunc(PRIVMSG, func(conn *Conn, line *Line) {
		panic("panic!")
	})
	c.in <- parseLine(":nick!user@host.com PRIVMSG #channel :OH NO PIGEONS")
	recovered.assertWasCalled("Failed to recover panic!")
}
<commit_msg>Atomic incrementing of call count for dispatch test (1->0).<commit_after>package client

import (
	"sync/atomic"
	"testing"
	"time"
)

func TestHandlerSet(t *testing.T) {
	// A Conn is needed here because the previous behaviour of passing nil to
	// hset.dispatch causes a nil pointer dereference with panic recovery.
	c, s := setUp(t)
	defer s.tearDown()

	hs := handlerSet()
	if len(hs.set) != 0 {
		t.Errorf("New set contains things!")
	}

	callcount := new(int32)
	f := func(_ *Conn, _ *Line) {
		atomic.AddInt32(callcount, 1)
	}

	// Add one
	hn1 := hs.add("ONE", HandlerFunc(f)).(*hNode)
	hl, ok := hs.set["one"]
	if len(hs.set) != 1 || !ok {
		t.Errorf("Set doesn't contain 'one' list after add().")
	}
	if hn1.set != hs || hn1.event != "one" || hn1.prev != nil || hn1.next != nil {
		t.Errorf("First node for 'one' not created correctly")
	}
	if hl.start != hn1 || hl.end != hn1 {
		t.Errorf("Node not added to empty 'one' list correctly.")
	}

	// Add another one...
	hn2 := hs.add("one", HandlerFunc(f)).(*hNode)
	if len(hs.set) != 1 {
		t.Errorf("Set contains more than 'one' list after add().")
	}
	if hn2.set != hs || hn2.event != "one" {
		t.Errorf("Second node for 'one' not created correctly")
	}
	if hn1.prev != nil || hn1.next != hn2 || hn2.prev != hn1 || hn2.next != nil {
		t.Errorf("Nodes for 'one' not linked correctly.")
	}
	if hl.start != hn1 || hl.end != hn2 {
		t.Errorf("Node not appended to 'one' list correctly.")
	}

	// Add a third one!
	hn3 := hs.add("one", HandlerFunc(f)).(*hNode)
	if len(hs.set) != 1 {
		t.Errorf("Set contains more than 'one' list after add().")
	}
	if hn3.set != hs || hn3.event != "one" {
		t.Errorf("Third node for 'one' not created correctly")
	}
	if hn1.prev != nil || hn1.next != hn2 ||
		hn2.prev != hn1 || hn2.next != hn3 ||
		hn3.prev != hn2 || hn3.next != nil {
		t.Errorf("Nodes for 'one' not linked correctly.")
	}
	if hl.start != hn1 || hl.end != hn3 {
		t.Errorf("Node not appended to 'one' list correctly.")
	}

	// And finally a fourth one!
	hn4 := hs.add("one", HandlerFunc(f)).(*hNode)
	if len(hs.set) != 1 {
		t.Errorf("Set contains more than 'one' list after add().")
	}
	if hn4.set != hs || hn4.event != "one" {
		t.Errorf("Fourth node for 'one' not created correctly.")
	}
	if hn1.prev != nil || hn1.next != hn2 ||
		hn2.prev != hn1 || hn2.next != hn3 ||
		hn3.prev != hn2 || hn3.next != hn4 ||
		hn4.prev != hn3 || hn4.next != nil {
		t.Errorf("Nodes for 'one' not linked correctly.")
	}
	if hl.start != hn1 || hl.end != hn4 {
		t.Errorf("Node not appended to 'one' list correctly.")
	}

	// Dispatch should result in 4 additions.
	if atomic.LoadInt32(callcount) != 0 {
		t.Errorf("Something incremented call count before we were expecting it.")
	}
	hs.dispatch(c, &Line{Cmd: "One"})
	<-time.After(time.Millisecond)
	if atomic.LoadInt32(callcount) != 4 {
		t.Errorf("Our handler wasn't called four times :-(")
	}

	// Remove node 3.
	hn3.Remove()
	if len(hs.set) != 1 {
		t.Errorf("Set list count changed after remove().")
	}
	if hn3.set != nil || hn3.prev != nil || hn3.next != nil {
		t.Errorf("Third node for 'one' not removed correctly.")
	}
	if hn1.prev != nil || hn1.next != hn2 ||
		hn2.prev != hn1 || hn2.next != hn4 ||
		hn4.prev != hn2 || hn4.next != nil {
		t.Errorf("Third node for 'one' not unlinked correctly.")
	}
	if hl.start != hn1 || hl.end != hn4 {
		t.Errorf("Third node for 'one' changed list pointers.")
	}

	// Dispatch should result in 3 additions.
	hs.dispatch(c, &Line{Cmd: "One"})
	<-time.After(time.Millisecond)
	if atomic.LoadInt32(callcount) != 7 {
		t.Errorf("Our handler wasn't called three times :-(")
	}

	// Remove node 1.
	hs.remove(hn1)
	if len(hs.set) != 1 {
		t.Errorf("Set list count changed after remove().")
	}
	if hn1.set != nil || hn1.prev != nil || hn1.next != nil {
		t.Errorf("First node for 'one' not removed correctly.")
	}
	if hn2.prev != nil || hn2.next != hn4 || hn4.prev != hn2 || hn4.next != nil {
		t.Errorf("First node for 'one' not unlinked correctly.")
	}
	if hl.start != hn2 || hl.end != hn4 {
		t.Errorf("First node for 'one' didn't change list pointers.")
	}

	// Dispatch should result in 2 additions.
	hs.dispatch(c, &Line{Cmd: "One"})
	<-time.After(time.Millisecond)
	if atomic.LoadInt32(callcount) != 9 {
		t.Errorf("Our handler wasn't called two times :-(")
	}

	// Remove node 4.
	hn4.Remove()
	if len(hs.set) != 1 {
		t.Errorf("Set list count changed after remove().")
	}
	if hn4.set != nil || hn4.prev != nil || hn4.next != nil {
		t.Errorf("Fourth node for 'one' not removed correctly.")
	}
	if hn2.prev != nil || hn2.next != nil {
		t.Errorf("Fourth node for 'one' not unlinked correctly.")
	}
	if hl.start != hn2 || hl.end != hn2 {
		t.Errorf("Fourth node for 'one' didn't change list pointers.")
	}

	// Dispatch should result in 1 addition.
	hs.dispatch(c, &Line{Cmd: "One"})
	<-time.After(time.Millisecond)
	if atomic.LoadInt32(callcount) != 10 {
		t.Errorf("Our handler wasn't called once :-(")
	}

	// Remove node 2.
	hs.remove(hn2)
	if len(hs.set) != 0 {
		t.Errorf("Removing last node in 'one' didn't remove list.")
	}
	if hn2.set != nil || hn2.prev != nil || hn2.next != nil {
		t.Errorf("Second node for 'one' not removed correctly.")
	}
	if hl.start != nil || hl.end != nil {
		t.Errorf("Second node for 'one' didn't change list pointers.")
	}

	// Dispatch should result in NO additions.
	hs.dispatch(c, &Line{Cmd: "One"})
	<-time.After(time.Millisecond)
	if atomic.LoadInt32(callcount) != 10 {
		t.Errorf("Our handler was called?")
	}
}

func TestPanicRecovery(t *testing.T) {
	c, s := setUp(t)
	defer s.tearDown()

	recovered := callCheck(t)
	c.cfg.Recover = func(conn *Conn, line *Line) {
		if err, ok := recover().(string); ok && err == "panic!" {
			recovered.call()
		}
	}
	c.HandleFunc(PRIVMSG, func(conn *Conn, line *Line) {
		panic("panic!")
	})
	c.in <- parseLine(":nick!user@host.com PRIVMSG #channel :OH NO PIGEONS")
	recovered.assertWasCalled("Failed to recover panic!")
}
<|endoftext|>


<commit_before>package mp

// #include <mpv/client.h>
// #include <stdlib.h>
// #cgo LDFLAGS: -lmpv
//
// /* some helper functions for string arrays */
// char** makeCharArray(int size) {
//     return calloc(sizeof(char*), size);
// }
// void setArrayString(char** a, int i, char* s) {
//     a[i] = s;
// }
import "C"
import "unsafe"

import (
	"errors"
	"flag"
	"fmt"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/aykevl/plaincast/config"
	"github.com/aykevl/plaincast/log"
)

var MPV_PROPERTY_UNAVAILABLE = errors.New("mpv: property unavailable")

// MPV is an implementation of Backend, using libmpv.
type MPV struct {
	handle       *C.mpv_handle
	running      bool
	runningMutex sync.Mutex
	mainloopExit chan struct{}
}

var mpvLogger = log.New("mpv", "log MPV wrapper output")
var logLibMPV = flag.Bool("log-libmpv", false, "log output of libmpv")

// New creates a new MPV instance and initializes the libmpv player
func (mpv *MPV) initialize() (chan State, int) {
	if mpv.handle != nil || mpv.running {
		panic("already initialized")
	}

	mpv.mainloopExit = make(chan struct{})
	mpv.running = true

	mpv.handle = C.mpv_create()

	conf := config.Get()
	initialVolume, err := conf.GetInt("player.mpv.volume", func() (int, error) {
		return INITIAL_VOLUME, nil
	})
	if err != nil {
		// should not happen
		panic(err)
	}

	mpv.setOptionFlag("no-resume-playback", true)
	//mpv.setOptionString("softvol", "yes")
	//mpv.setOptionString("ao", "pulse")
	mpv.setOptionInt("volume", initialVolume)

	// Disable video in three ways.
	mpv.setOptionFlag("no-video", true)
	mpv.setOptionString("vo", "null")
	mpv.setOptionString("vid", "no")

	// Cache settings assume 128kbps audio stream (16kByte/s).
	// The default is a cache size of 25MB, these are somewhat more sensible
	// cache sizes IMO.
	mpv.setOptionInt("cache-default", 160) // 10 seconds
	mpv.setOptionInt("cache-seek-min", 16) // 1 second

	// Some extra debugging information, but don't read from stdin.
	// libmpv has a problem with signal handling, though: when `terminal` is
	// true, Ctrl+C doesn't work correctly anymore and program output is
	// disabled.
	mpv.setOptionFlag("terminal", *logLibMPV)
	mpv.setOptionFlag("no-input-terminal", true)
	mpv.setOptionFlag("quiet", true)

	mpv.checkError(C.mpv_initialize(mpv.handle))

	eventChan := make(chan State)

	go mpv.eventHandler(eventChan)

	return eventChan, initialVolume
}

// Function quit quits the player.
// WARNING: This MUST be the last call on this media player.
func (mpv *MPV) quit() {
	mpv.runningMutex.Lock()
	if !mpv.running {
		panic("quit called twice")
	}
	mpv.running = false
	mpv.runningMutex.Unlock()

	// Wake up the event handler mainloop, probably sending the MPV_EVENT_NONE
	// signal.
	// See mpv_wait_event below: this doesn't work yet (it uses a workaround
	// now).
	//C.mpv_wakeup(handle)

	// Wait until the mainloop has exited.
	<-mpv.mainloopExit

	// Actually destroy the MPV player. This blocks until the player has been
	// fully brought down.
	handle := mpv.handle
	mpv.handle = nil // make it easier to catch race conditions
	C.mpv_terminate_destroy(handle)
}

// setOptionFlag passes a boolean flag to mpv
func (mpv *MPV) setOptionFlag(key string, value bool) {
	cValue := C.int(0)
	if value {
		cValue = 1
	}

	mpv.setOption(key, C.MPV_FORMAT_FLAG, unsafe.Pointer(&cValue))
}

// setOptionInt passes an integer option to mpv
func (mpv *MPV) setOptionInt(key string, value int) {
	cValue := C.int64_t(value)
	mpv.setOption(key, C.MPV_FORMAT_INT64, unsafe.Pointer(&cValue))
}

// setOptionString passes a string option to mpv
func (mpv *MPV) setOptionString(key, value string) {
	cValue := C.CString(value)
	defer C.free(unsafe.Pointer(cValue))

	mpv.setOption(key, C.MPV_FORMAT_STRING, unsafe.Pointer(&cValue))
}

// setOption is a generic function to pass options to mpv
func (mpv *MPV) setOption(key string, format C.mpv_format, value unsafe.Pointer) {
	cKey := C.CString(key)
	defer C.free(unsafe.Pointer(cKey))

	mpv.checkError(C.mpv_set_option(mpv.handle, cKey, format, value))
}

// sendCommand sends a command to the libmpv player
func (mpv *MPV) sendCommand(command []string) {
	// Print command, but without the stream
	cmd := make([]string, len(command))
	copy(cmd, command)
	if command[0] == "loadfile" {
		cmd[1] = "<stream>"
	}
	logger.Println("MPV command:", cmd)

	cArray := C.makeCharArray(C.int(len(command) + 1))
	if cArray == nil {
		panic("got NULL from calloc")
	}
	defer C.free(unsafe.Pointer(cArray))

	for i, s := range command {
		cStr := C.CString(s)
		C.setArrayString(cArray, C.int(i), cStr)
		defer C.free(unsafe.Pointer(cStr))
	}

	mpv.checkError(C.mpv_command_async(mpv.handle, 0, cArray))
}

// getProperty returns the MPV player property as a string
// Warning: this function can take an unbounded time. Call inside a new
// goroutine to prevent blocking / deadlocks.
func (mpv *MPV) getProperty(name string) (float64, error) {
	logger.Printf("MPV get property: %s\n", name)

	cName := C.CString(name)
	defer C.free(unsafe.Pointer(cName))

	var cValue C.double
	status := C.mpv_get_property(mpv.handle, cName, C.MPV_FORMAT_DOUBLE, unsafe.Pointer(&cValue))
	if status == C.MPV_ERROR_PROPERTY_UNAVAILABLE {
		return 0, MPV_PROPERTY_UNAVAILABLE
	} else if status != 0 {
		return 0, errors.New("mpv: " + C.GoString(C.mpv_error_string(status)))
	}

	return float64(cValue), nil
}

// setProperty sets the MPV player property
func (mpv *MPV) setProperty(name, value string) {
	logger.Printf("MPV set property: %s=%s\n", name, value)

	cName := C.CString(name)
	defer C.free(unsafe.Pointer(cName))
	cValue := C.CString(value)
	defer C.free(unsafe.Pointer(cValue))

	// setProperty can take an unbounded time, don't block here using _async
	// TODO: use some form of error handling. Sometimes, it is impossible to
	// know beforehand whether setting a property will cause an error.
	// Importantly, catch the 'property unavailable' error.
	mpv.checkError(C.mpv_set_property_async(mpv.handle, 1, cName, C.MPV_FORMAT_STRING, unsafe.Pointer(&cValue)))
}

func (mpv *MPV) play(stream string, position time.Duration, volume int) {
	options := "pause=no"

	if position != 0 {
		options += fmt.Sprintf(",start=%.3f", position.Seconds())
	}

	if volume >= 0 {
		options += fmt.Sprintf(",volume=%d", volume)
	}

	// The proxy is a workaround for misbehaving libav/libnettle that appear to
	// try to read the whole HTTP response before closing the connection. Go has
	// a better HTTPS implementation, which is used here as a workaround.
	// This libav/libnettle combination is in use on Debian jessie. FFmpeg
	// doesn't have a problem with it.
	if !strings.HasPrefix(stream, "https://") {
		logger.Panic("Stream does not start with https://...")
	}
	mpv.sendCommand([]string{"loadfile", "http://localhost:8008/proxy/" + stream[len("https://"):], "replace", options})
}

func (mpv *MPV) pause() {
	mpv.setProperty("pause", "yes")
}

func (mpv *MPV) resume() {
	mpv.setProperty("pause", "no")
}

func (mpv *MPV) getDuration() (time.Duration, error) {
	duration, err := mpv.getProperty("duration")
	if err == MPV_PROPERTY_UNAVAILABLE {
		return 0, PROPERTY_UNAVAILABLE
	} else if err != nil {
		// should not happen
		panic(err)
	}

	return time.Duration(duration * float64(time.Second)), nil
}

func (mpv *MPV) getPosition() (time.Duration, error) {
	position, err := mpv.getProperty("time-pos")
	if err == MPV_PROPERTY_UNAVAILABLE {
		return 0, PROPERTY_UNAVAILABLE
	} else if err != nil {
		// should not happen
		panic(err)
	}

	if position < 0 {
		// Sometimes, the position appears to be slightly off.
		position = 0
	}

	return time.Duration(position * float64(time.Second)), nil
}

func (mpv *MPV) setPosition(position time.Duration) {
	mpv.sendCommand([]string{"seek", fmt.Sprintf("%.3f", position.Seconds()), "absolute"})
}

func (mpv *MPV) getVolume() int {
	volume, err := mpv.getProperty("volume")
	if err != nil {
		// should not happen
		panic(err)
	}

	return int(volume + 0.5)
}

func (mpv *MPV) setVolume(volume int) {
	mpv.setProperty("volume", strconv.Itoa(volume))
	config.Get().SetInt("player.mpv.volume", volume)
}

func (mpv *MPV) stop() {
	mpv.sendCommand([]string{"stop"})
}

// playerEventHandler waits for libmpv player events and sends them on a channel
func (mpv *MPV) eventHandler(eventChan chan State) {
	for {
		// wait until there is an event (negative timeout means infinite timeout)
		// The timeout is 1 second to work around libmpv bug #1372 (mpv_wakeup
		// does not actually wake up mpv_wait_event). It keeps checking every
		// second whether MPV has exited.
		// TODO revert this as soon as the fix for that bug lands in a stable
		// release. Check for the problematic versions and keep the old behavior
		// for older MPV versions.
		event := C.mpv_wait_event(mpv.handle, 1)
		if event.event_id != C.MPV_EVENT_NONE {
			logger.Printf("MPV event: %s (%d)\n", C.GoString(C.mpv_event_name(event.event_id)), int(event.event_id))
		}

		if event.error != 0 {
			panic("MPV API error")
		}

		mpv.runningMutex.Lock()
		running := mpv.running
		mpv.runningMutex.Unlock()

		if !running {
			close(eventChan)
			mpv.mainloopExit <- struct{}{}
			return
		}

		switch event.event_id {
		case C.MPV_EVENT_PLAYBACK_RESTART:
			eventChan <- STATE_PLAYING
		case C.MPV_EVENT_END_FILE:
			eventChan <- STATE_STOPPED
		case C.MPV_EVENT_PAUSE:
			eventChan <- STATE_PAUSED
		case C.MPV_EVENT_UNPAUSE:
			eventChan <- STATE_PLAYING
		}
	}
}

// checkError checks for libmpv errors and panics if it finds one
func (mpv *MPV) checkError(status C.int) {
	if status < 0 {
		// this C string should not be freed (it is static)
		panic(fmt.Sprintf("mpv API error: %s (%d)", C.GoString(C.mpv_error_string(status)), int(status)))
	}
}
<commit_msg>Fix compatibility with mpv 1.27<commit_after>package mp

// #include <mpv/client.h>
// #include <stdlib.h>
// #cgo LDFLAGS: -lmpv
//
// /* some helper functions for string arrays */
// char** makeCharArray(int size) {
//     return calloc(sizeof(char*), size);
// }
// void setArrayString(char** a, int i, char* s) {
//     a[i] = s;
// }
import "C"
import "unsafe"

import (
	"errors"
	"flag"
	"fmt"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/aykevl/plaincast/config"
	"github.com/aykevl/plaincast/log"
)

var MPV_PROPERTY_UNAVAILABLE = errors.New("mpv: property unavailable")

// MPV is an implementation of Backend, using libmpv.
type MPV struct {
	handle       *C.mpv_handle
	running      bool
	runningMutex sync.Mutex
	mainloopExit chan struct{}
}

var mpvLogger = log.New("mpv", "log MPV wrapper output")
var logLibMPV = flag.Bool("log-libmpv", false, "log output of libmpv")

// New creates a new MPV instance and initializes the libmpv player
func (mpv *MPV) initialize() (chan State, int) {
	if mpv.handle != nil || mpv.running {
		panic("already initialized")
	}

	mpv.mainloopExit = make(chan struct{})
	mpv.running = true

	mpv.handle = C.mpv_create()

	conf := config.Get()
	initialVolume, err := conf.GetInt("player.mpv.volume", func() (int, error) {
		return INITIAL_VOLUME, nil
	})
	if err != nil {
		// should not happen
		panic(err)
	}

	mpv.setOptionFlag("resume-playback", false)
	//mpv.setOptionString("softvol", "yes")
	//mpv.setOptionString("ao", "pulse")
	mpv.setOptionInt("volume", initialVolume)

	// Disable video in three ways.
	mpv.setOptionFlag("video", false)
	mpv.setOptionString("vo", "null")
	mpv.setOptionString("vid", "no")

	// Cache settings assume 128kbps audio stream (16kByte/s).
	// The default is a cache size of 25MB, these are somewhat more sensible
	// cache sizes IMO.
	mpv.setOptionInt("cache-default", 160) // 10 seconds
	mpv.setOptionInt("cache-seek-min", 16) // 1 second

	// Some extra debugging information, but don't read from stdin.
	// libmpv has a problem with signal handling, though: when `terminal` is
	// true, Ctrl+C doesn't work correctly anymore and program output is
	// disabled.
	mpv.setOptionFlag("terminal", *logLibMPV)
	mpv.setOptionFlag("input-terminal", false)
	mpv.setOptionFlag("quiet", true)

	mpv.checkError(C.mpv_initialize(mpv.handle))

	eventChan := make(chan State)

	go mpv.eventHandler(eventChan)

	return eventChan, initialVolume
}

// Function quit quits the player.
// WARNING: This MUST be the last call on this media player.
func (mpv *MPV) quit() {
	mpv.runningMutex.Lock()
	if !mpv.running {
		panic("quit called twice")
	}
	mpv.running = false
	mpv.runningMutex.Unlock()

	// Wake up the event handler mainloop, probably sending the MPV_EVENT_NONE
	// signal.
	// See mpv_wait_event below: this doesn't work yet (it uses a workaround
	// now).
	//C.mpv_wakeup(handle)

	// Wait until the mainloop has exited.
	<-mpv.mainloopExit

	// Actually destroy the MPV player. This blocks until the player has been
	// fully brought down.
	handle := mpv.handle
	mpv.handle = nil // make it easier to catch race conditions
	C.mpv_terminate_destroy(handle)
}

// setOptionFlag passes a boolean flag to mpv
func (mpv *MPV) setOptionFlag(key string, value bool) {
	cValue := C.int(0)
	if value {
		cValue = 1
	}

	mpv.setOption(key, C.MPV_FORMAT_FLAG, unsafe.Pointer(&cValue))
}

// setOptionInt passes an integer option to mpv
func (mpv *MPV) setOptionInt(key string, value int) {
	cValue := C.int64_t(value)
	mpv.setOption(key, C.MPV_FORMAT_INT64, unsafe.Pointer(&cValue))
}

// setOptionString passes a string option to mpv
func (mpv *MPV) setOptionString(key, value string) {
	cValue := C.CString(value)
	defer C.free(unsafe.Pointer(cValue))

	mpv.setOption(key, C.MPV_FORMAT_STRING, unsafe.Pointer(&cValue))
}

// setOption is a generic function to pass options to mpv
func (mpv *MPV) setOption(key string, format C.mpv_format, value unsafe.Pointer) {
	cKey := C.CString(key)
	defer C.free(unsafe.Pointer(cKey))

	mpv.checkError(C.mpv_set_option(mpv.handle, cKey, format, value))
}

// sendCommand sends a command to the libmpv player
func (mpv *MPV) sendCommand(command []string) {
	// Print command, but without the stream
	cmd := make([]string, len(command))
	copy(cmd, command)
	if command[0] == "loadfile" {
		cmd[1] = "<stream>"
	}
	logger.Println("MPV command:", cmd)

	cArray := C.makeCharArray(C.int(len(command) + 1))
	if cArray == nil {
		panic("got NULL from calloc")
	}
	defer C.free(unsafe.Pointer(cArray))

	for i, s := range command {
		cStr := C.CString(s)
		C.setArrayString(cArray, C.int(i), cStr)
		defer C.free(unsafe.Pointer(cStr))
	}

	mpv.checkError(C.mpv_command_async(mpv.handle, 0, cArray))
}

// getProperty returns the MPV player property as a string
// Warning: this function can take an unbounded time. Call inside a new
// goroutine to prevent blocking / deadlocks.
func (mpv *MPV) getProperty(name string) (float64, error) {
	logger.Printf("MPV get property: %s\n", name)

	cName := C.CString(name)
	defer C.free(unsafe.Pointer(cName))

	var cValue C.double
	status := C.mpv_get_property(mpv.handle, cName, C.MPV_FORMAT_DOUBLE, unsafe.Pointer(&cValue))
	if status == C.MPV_ERROR_PROPERTY_UNAVAILABLE {
		return 0, MPV_PROPERTY_UNAVAILABLE
	} else if status != 0 {
		return 0, errors.New("mpv: " + C.GoString(C.mpv_error_string(status)))
	}

	return float64(cValue), nil
}

// setProperty sets the MPV player property
func (mpv *MPV) setProperty(name, value string) {
	logger.Printf("MPV set property: %s=%s\n", name, value)

	cName := C.CString(name)
	defer C.free(unsafe.Pointer(cName))
	cValue := C.CString(value)
	defer C.free(unsafe.Pointer(cValue))

	// setProperty can take an unbounded time, don't block here using _async
	// TODO: use some form of error handling. Sometimes, it is impossible to
	// know beforehand whether setting a property will cause an error.
	// Importantly, catch the 'property unavailable' error.
	mpv.checkError(C.mpv_set_property_async(mpv.handle, 1, cName, C.MPV_FORMAT_STRING, unsafe.Pointer(&cValue)))
}

func (mpv *MPV) play(stream string, position time.Duration, volume int) {
	options := "pause=no"

	if position != 0 {
		options += fmt.Sprintf(",start=%.3f", position.Seconds())
	}

	if volume >= 0 {
		options += fmt.Sprintf(",volume=%d", volume)
	}

	// The proxy is a workaround for misbehaving libav/libnettle that appear to
	// try to read the whole HTTP response before closing the connection. Go has
	// a better HTTPS implementation, which is used here as a workaround.
	// This libav/libnettle combination is in use on Debian jessie. FFmpeg
	// doesn't have a problem with it.
	if !strings.HasPrefix(stream, "https://") {
		logger.Panic("Stream does not start with https://...")
	}
	mpv.sendCommand([]string{"loadfile", "http://localhost:8008/proxy/" + stream[len("https://"):], "replace", options})
}

func (mpv *MPV) pause() {
	mpv.setProperty("pause", "yes")
}

func (mpv *MPV) resume() {
	mpv.setProperty("pause", "no")
}

func (mpv *MPV) getDuration() (time.Duration, error) {
	duration, err := mpv.getProperty("duration")
	if err == MPV_PROPERTY_UNAVAILABLE {
		return 0, PROPERTY_UNAVAILABLE
	} else if err != nil {
		// should not happen
		panic(err)
	}

	return time.Duration(duration * float64(time.Second)), nil
}

func (mpv *MPV) getPosition() (time.Duration, error) {
	position, err := mpv.getProperty("time-pos")
	if err == MPV_PROPERTY_UNAVAILABLE {
		return 0, PROPERTY_UNAVAILABLE
	} else if err != nil {
		// should not happen
		panic(err)
	}

	if position < 0 {
		// Sometimes, the position appears to be slightly off.
		position = 0
	}

	return time.Duration(position * float64(time.Second)), nil
}

func (mpv *MPV) setPosition(position time.Duration) {
	mpv.sendCommand([]string{"seek", fmt.Sprintf("%.3f", position.Seconds()), "absolute"})
}

func (mpv *MPV) getVolume() int {
	volume, err := mpv.getProperty("volume")
	if err != nil {
		// should not happen
		panic(err)
	}

	return int(volume + 0.5)
}

func (mpv *MPV) setVolume(volume int) {
	mpv.setProperty("volume", strconv.Itoa(volume))
	config.Get().SetInt("player.mpv.volume", volume)
}

func (mpv *MPV) stop() {
	mpv.sendCommand([]string{"stop"})
}

// playerEventHandler waits for libmpv player events and sends them on a channel
func (mpv *MPV) eventHandler(eventChan chan State) {
	for {
		// wait until there is an event (negative timeout means infinite timeout)
		// The timeout is 1 second to work around libmpv bug #1372 (mpv_wakeup
		// does not actually wake up mpv_wait_event). It keeps checking every
		// second whether MPV has exited.
		// TODO revert this as soon as the fix for that bug lands in a stable
		// release. Check for the problematic versions and keep the old behavior
		// for older MPV versions.
		event := C.mpv_wait_event(mpv.handle, 1)
		if event.event_id != C.MPV_EVENT_NONE {
			logger.Printf("MPV event: %s (%d)\n", C.GoString(C.mpv_event_name(event.event_id)), int(event.event_id))
		}

		if event.error != 0 {
			panic("MPV API error")
		}

		mpv.runningMutex.Lock()
		running := mpv.running
		mpv.runningMutex.Unlock()

		if !running {
			close(eventChan)
			mpv.mainloopExit <- struct{}{}
			return
		}

		switch event.event_id {
		case C.MPV_EVENT_PLAYBACK_RESTART:
			eventChan <- STATE_PLAYING
		case C.MPV_EVENT_END_FILE:
			eventChan <- STATE_STOPPED
		case C.MPV_EVENT_PAUSE:
			eventChan <- STATE_PAUSED
		case C.MPV_EVENT_UNPAUSE:
			eventChan <- STATE_PLAYING
		}
	}
}

// checkError checks for libmpv errors and panics if it finds one
func (mpv *MPV) checkError(status C.int) {
	if status < 0 {
		// this C string should not be freed (it is static)
		panic(fmt.Sprintf("mpv API error: %s (%d)", C.GoString(C.mpv_error_string(status)), int(status)))
	}
}
<|endoftext|>


<commit_before>package godns

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"html/template"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"strings"

	"golang.org/x/net/proxy"
	gomail "gopkg.in/gomail.v2"
)

var (
	// Logo for GoDNS
	Logo = `

 ██████╗  ██████╗ ██████╗ ███╗   ██╗███████╗
██╔════╝ ██╔═══██╗██╔══██╗████╗  ██║██╔════╝
██║  ███╗██║   ██║██║  ██║██╔██╗ ██║███████╗
██║   ██║██║   ██║██║  ██║██║╚██╗██║╚════██║
╚██████╔╝╚██████╔╝██████╔╝██║ ╚████║███████║
 ╚═════╝  ╚═════╝ ╚═════╝ ╚═╝  ╚═══╝╚══════╝

GoDNS V%s
https://github.com/TimothyYe/godns

`
)

const (
	// PanicMax is the max allowed panic times
	PanicMax = 5
	// DNSPOD for dnspod.cn
	DNSPOD = "DNSPod"
	// HE for he.net
	HE = "HE"
	// CLOUDFLARE for cloudflare.com
	CLOUDFLARE = "Cloudflare"
	// ALIDNS for AliDNS
	ALIDNS = "AliDNS"
	// GOOGLE for Google Domains
	GOOGLE = "Google"
	// DUCK for Duck DNS
	DUCK = "DuckDNS"
	// IPV4 for IPV4 mode
	IPV4 = "IPV4"
	// IPV6 for IPV6 mode
	IPV6 = "IPV6"
)

//GetIPFromInterface gets IP address from the specific interface
func GetIPFromInterface(configuration *Settings) (string, error) {
	ifaces, err := net.InterfaceByName(configuration.IPInterface)
	if err != nil {
		log.Println("can't get network device "+configuration.IPInterface+":", err)
		return "", err
	}

	addrs, err := ifaces.Addrs()
	if err != nil {
		log.Println("can't get address from "+configuration.IPInterface+":", err)
		return "", err
	}

	for _, addr := range addrs {
		var ip net.IP
		switch v := addr.(type) {
		case *net.IPNet:
			ip = v.IP
		case *net.IPAddr:
			ip = v.IP
		}
		if ip == nil {
			continue
		}

		if !(ip.IsGlobalUnicast() &&
			!(ip.IsUnspecified() ||
				ip.IsMulticast() ||
				ip.IsLoopback() ||
				ip.IsLinkLocalUnicast() ||
				ip.IsLinkLocalMulticast() ||
				ip.IsInterfaceLocalMulticast())) {
			continue
		}

		if isIPv4(ip.String()) {
			if strings.ToUpper(configuration.IPType) == IPV4 {
				continue
			}
		} else {
			if strings.ToUpper(configuration.IPType) != IPV6 {
				continue
			}
		}

		return ip.String(), nil

	}
	return "", errors.New("can't get a vaild address from " + configuration.IPInterface)
}

func isIPv4(ip string) bool {
	return strings.Count(ip, ":") < 2
}

// GetHttpClient creates the HTTP client and return it
func GetHttpClient(configuration *Settings) *http.Client {
	client := &http.Client{}

	if configuration.Socks5Proxy != "" {
		log.Println("use socks5 proxy:" + configuration.Socks5Proxy)
		dialer, err := proxy.SOCKS5("tcp", configuration.Socks5Proxy, nil, proxy.Direct)
		if err != nil {
			log.Println("can't connect to the proxy:", err)
			return nil
		}

		httpTransport := &http.Transport{}
		client.Transport = httpTransport
		httpTransport.Dial = dialer.Dial
	}

	return client
}

//GetCurrentIP gets an IP from either internet or specific interface, depending on configuration
func GetCurrentIP(configuration *Settings) (string, error) {
	var err error

	if configuration.IPUrl != "" || configuration.IPV6Url != "" {
		ip, err := GetIPOnline(configuration)
		if err != nil {
			log.Println("get ip online failed. Fallback to get ip from interface if possible.")
		} else {
			return ip, nil
		}
	}

	if configuration.IPInterface != "" {
		ip, err := GetIPFromInterface(configuration)
		if err != nil {
			log.Println("get ip from interface failed. There is no more ways to try.")
		} else {
			return ip, nil
		}
	}

	return "", err
}

// GetIPOnline gets public IP from internet
func GetIPOnline(configuration *Settings) (string, error) {
	client := &http.Client{}

	var response *http.Response
	var err error

	if configuration.IPType == "" || strings.ToUpper(configuration.IPType) == IPV4 {
		response, err = client.Get(configuration.IPUrl)
	} else {
		response, err = client.Get(configuration.IPV6Url)
	}

	if err != nil {
		log.Println("Cannot get IP...")
		return "", err
	}

	defer response.Body.Close()

	body, _ := ioutil.ReadAll(response.Body)
	return strings.Trim(string(body), "\n"), nil
}

// CheckSettings check the format of settings
func CheckSettings(config *Settings) error {
	if config.Provider == DNSPOD {
		if config.Password == "" && config.LoginToken == "" {
			return errors.New("password or login token cannot be empty")
		}
	} else if config.Provider == HE {
		if config.Password == "" {
			return errors.New("password cannot be empty")
		}
	} else if config.Provider == CLOUDFLARE {
		if config.LoginToken == "" {
			if config.Email == "" {
				return errors.New("email cannot be empty")
			}
			if config.Password == "" {
				return errors.New("password cannot be empty")
			}
		}
	} else if config.Provider == ALIDNS {
		if config.Email == "" {
			return errors.New("email cannot be empty")
		}
		if config.Password == "" {
			return errors.New("password cannot be empty")
		}
	} else if config.Provider == DUCK {
		if config.LoginToken == "" {
			return errors.New("login token cannot be empty")
		}
	} else if config.Provider == GOOGLE {
		if config.Email == "" {
			return errors.New("email cannot be empty")
		}
		if config.Password == "" {
			return errors.New("password cannot be empty")
		}
	} else {
		return errors.New("please provide supported DNS provider: DNSPod/HE/AliDNS/Cloudflare/GoogleDomain/DuckDNS")
	}

	return nil
}

// SendNotify sends notify if IP is changed
func SendTelegramNotify(configuration *Settings, domain, currentIP string) error {
	if ! configuration.Notify.Telegram.Enabled {
		return nil
	}

	if configuration.Notify.Telegram.BotApiKey == "" {
		return errors.New("bot api key cannot be empty")
	}

	if configuration.Notify.Telegram.ChatId == "" {
		return errors.New("chat id cannot be empty")
	}

	client := GetHttpClient(configuration)
	tpl := configuration.Notify.Telegram.MsgTemplate
	if tpl == "" {
		tpl = "_Your IP address is changed to_%0A%0A*{{ .CurrentIP }}*%0A%0ADomain *{{ .Domain }}* is updated"
	}

	msg := buildTemplate(currentIP, domain, tpl)
	url := fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage?chat_id=%s&parse_mode=Markdown&text=%s",
			configuration.Notify.Telegram.BotApiKey,
			configuration.Notify.Telegram.ChatId,
			msg)
	var response *http.Response
	var err error

	response, err = client.Get(url)

	if err != nil {
		return err
	}

	defer response.Body.Close()

	body, _ := ioutil.ReadAll(response.Body)
	type ResponseParameters struct {
		MigrateToChatID int64 `json:"migrate_to_chat_id"` // optional
		RetryAfter      int   `json:"retry_after"`        // optional
	}
	type APIResponse struct {
		Ok          bool                `json:"ok"`
		Result      json.RawMessage     `json:"result"`
		ErrorCode   int                 `json:"error_code"`
		Description string              `json:"description"`
		Parameters  *ResponseParameters `json:"parameters"`
	}
	var resp APIResponse
	err = json.Unmarshal([]byte(body), &resp)
	if err != nil {
		fmt.Println("error:", err)
		return errors.New("Failed to parse response")
	}
	if ! resp.Ok {
		return errors.New(resp.Description)
	}

	return nil
}

// SendNotify sends mail notify if IP is changed
func SendMailNotify(configuration *Settings, domain, currentIP string) error {
	if ! configuration.Notify.Mail.Enabled {
		return nil
	}
	log.Print("Sending notification to:", configuration.Notify.Mail.SendTo)
	m := gomail.NewMessage()

	m.SetHeader("From", configuration.Notify.Mail.SMTPUsername)
	m.SetHeader("To", configuration.Notify.Mail.SendTo)
	m.SetHeader("Subject", "GoDNS Notification")
	log.Println("currentIP:", currentIP)
	log.Println("domain:", domain)
	m.SetBody("text/html", buildTemplate(currentIP, domain, mailTemplate))

	d := gomail.NewPlainDialer(configuration.Notify.Mail.SMTPServer, configuration.Notify.Mail.SMTPPort, configuration.Notify.Mail.SMTPUsername, configuration.Notify.Mail.SMTPPassword)

	// Send the email config by sendlist	.
	if err := d.DialAndSend(m); err != nil {
		return err
	}
	return nil
}

// SendNotify sends notify if IP is changed
func SendNotify(configuration *Settings, domain, currentIP string) error {
	err := SendTelegramNotify(configuration, domain, currentIP)
	if (err != nil) {
		log.Println("Send telegram notification with error:", err.Error())
	}
	err = SendMailNotify(configuration, domain, currentIP)
	if (err != nil) {
		log.Println("Send email notification with error:", err.Error())
	}
	return nil
}

func buildTemplate(currentIP, domain string, tplsrc string) string {
	t := template.New("notification template")
	if _, err := t.Parse(tplsrc); err != nil {
		log.Println("Failed to parse template")
		return ""
	}

	data := struct {
		CurrentIP string
		Domain    string
	}{
		currentIP,
		domain,
	}

	var tpl bytes.Buffer
	if err := t.Execute(&tpl, data); err != nil {
		log.Println(err.Error())
		return ""
	}

	return tpl.String()
}
<commit_msg>Use switch/case to make code easier to follow<commit_after>package godns

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"html/template"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"strings"

	"golang.org/x/net/proxy"
	gomail "gopkg.in/gomail.v2"
)

var (
	// Logo for GoDNS
	Logo = `

 ██████╗  ██████╗ ██████╗ ███╗   ██╗███████╗
██╔════╝ ██╔═══██╗██╔══██╗████╗  ██║██╔════╝
██║  ███╗██║   ██║██║  ██║██╔██╗ ██║███████╗
██║   ██║██║   ██║██║  ██║██║╚██╗██║╚════██║
╚██████╔╝╚██████╔╝██████╔╝██║ ╚████║███████║
 ╚═════╝  ╚═════╝ ╚═════╝ ╚═╝  ╚═══╝╚══════╝

GoDNS V%s
https://github.com/TimothyYe/godns

`
)

const (
	// PanicMax is the max allowed panic times
	PanicMax = 5
	// DNSPOD for dnspod.cn
	DNSPOD = "DNSPod"
	// HE for he.net
	HE = "HE"
	// CLOUDFLARE for cloudflare.com
	CLOUDFLARE = "Cloudflare"
	// ALIDNS for AliDNS
	ALIDNS = "AliDNS"
	// GOOGLE for Google Domains
	GOOGLE = "Google"
	// DUCK for Duck DNS
	DUCK = "DuckDNS"
	// IPV4 for IPV4 mode
	IPV4 = "IPV4"
	// IPV6 for IPV6 mode
	IPV6 = "IPV6"
)

//GetIPFromInterface gets IP address from the specific interface
func GetIPFromInterface(configuration *Settings) (string, error) {
	ifaces, err := net.InterfaceByName(configuration.IPInterface)
	if err != nil {
		log.Println("can't get network device "+configuration.IPInterface+":", err)
		return "", err
	}

	addrs, err := ifaces.Addrs()
	if err != nil {
		log.Println("can't get address from "+configuration.IPInterface+":", err)
		return "", err
	}

	for _, addr := range addrs {
		var ip net.IP
		switch v := addr.(type) {
		case *net.IPNet:
			ip = v.IP
		case *net.IPAddr:
			ip = v.IP
		}
		if ip == nil {
			continue
		}

		if !(ip.IsGlobalUnicast() &&
			!(ip.IsUnspecified() ||
				ip.IsMulticast() ||
				ip.IsLoopback() ||
				ip.IsLinkLocalUnicast() ||
				ip.IsLinkLocalMulticast() ||
				ip.IsInterfaceLocalMulticast())) {
			continue
		}

		if isIPv4(ip.String()) {
			if strings.ToUpper(configuration.IPType) == IPV4 {
				continue
			}
		} else {
			if strings.ToUpper(configuration.IPType) != IPV6 {
				continue
			}
		}

		return ip.String(), nil

	}
	return "", errors.New("can't get a vaild address from " + configuration.IPInterface)
}

func isIPv4(ip string) bool {
	return strings.Count(ip, ":") < 2
}

// GetHttpClient creates the HTTP client and return it
func GetHttpClient(configuration *Settings) *http.Client {
	client := &http.Client{}

	if configuration.Socks5Proxy != "" {
		log.Println("use socks5 proxy:" + configuration.Socks5Proxy)
		dialer, err := proxy.SOCKS5("tcp", configuration.Socks5Proxy, nil, proxy.Direct)
		if err != nil {
			log.Println("can't connect to the proxy:", err)
			return nil
		}

		httpTransport := &http.Transport{}
		client.Transport = httpTransport
		httpTransport.Dial = dialer.Dial
	}

	return client
}

//GetCurrentIP gets an IP from either internet or specific interface, depending on configuration
func GetCurrentIP(configuration *Settings) (string, error) {
	var err error

	if configuration.IPUrl != "" || configuration.IPV6Url != "" {
		ip, err := GetIPOnline(configuration)
		if err != nil {
			log.Println("get ip online failed. Fallback to get ip from interface if possible.")
		} else {
			return ip, nil
		}
	}

	if configuration.IPInterface != "" {
		ip, err := GetIPFromInterface(configuration)
		if err != nil {
			log.Println("get ip from interface failed. There is no more ways to try.")
		} else {
			return ip, nil
		}
	}

	return "", err
}

// GetIPOnline gets public IP from internet
func GetIPOnline(configuration *Settings) (string, error) {
	client := &http.Client{}

	var response *http.Response
	var err error

	if configuration.IPType == "" || strings.ToUpper(configuration.IPType) == IPV4 {
		response, err = client.Get(configuration.IPUrl)
	} else {
		response, err = client.Get(configuration.IPV6Url)
	}

	if err != nil {
		log.Println("Cannot get IP...")
		return "", err
	}

	defer response.Body.Close()

	body, _ := ioutil.ReadAll(response.Body)
	return strings.Trim(string(body), "\n"), nil
}

// CheckSettings check the format of settings
func CheckSettings(config *Settings) error {
	switch config.Provider {
	case DNSPOD:
		if config.Password == "" && config.LoginToken == "" {
			return errors.New("password or login token cannot be empty")
		}
	case HE:
		if config.Password == "" {
			return errors.New("password cannot be empty")
		}
	case CLOUDFLARE:
		if config.LoginToken == "" {
			if config.Email == "" {
				return errors.New("email cannot be empty")
			}
			if config.Password == "" {
				return errors.New("password cannot be empty")
			}
		}
	case ALIDNS:
		if config.Email == "" {
			return errors.New("email cannot be empty")
		}
		if config.Password == "" {
			return errors.New("password cannot be empty")
		}
	case DUCK:
		if config.LoginToken == "" {
			return errors.New("login token cannot be empty")
		}
	case GOOGLE:
		if config.Email == "" {
			return errors.New("email cannot be empty")
		}
		if config.Password == "" {
			return errors.New("password cannot be empty")
		}
	default:
		return errors.New("please provide supported DNS provider: DNSPod/HE/AliDNS/Cloudflare/GoogleDomain/DuckDNS")

	}

	return nil
}

// SendNotify sends notify if IP is changed
func SendTelegramNotify(configuration *Settings, domain, currentIP string) error {
	if !configuration.Notify.Telegram.Enabled {
		return nil
	}

	if configuration.Notify.Telegram.BotApiKey == "" {
		return errors.New("bot api key cannot be empty")
	}

	if configuration.Notify.Telegram.ChatId == "" {
		return errors.New("chat id cannot be empty")
	}

	client := GetHttpClient(configuration)
	tpl := configuration.Notify.Telegram.MsgTemplate
	if tpl == "" {
		tpl = "_Your IP address is changed to_%0A%0A*{{ .CurrentIP }}*%0A%0ADomain *{{ .Domain }}* is updated"
	}

	msg := buildTemplate(currentIP, domain, tpl)
	url := fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage?chat_id=%s&parse_mode=Markdown&text=%s",
		configuration.Notify.Telegram.BotApiKey,
		configuration.Notify.Telegram.ChatId,
		msg)
	var response *http.Response
	var err error

	response, err = client.Get(url)

	if err != nil {
		return err
	}

	defer response.Body.Close()

	body, _ := ioutil.ReadAll(response.Body)
	type ResponseParameters struct {
		MigrateToChatID int64 `json:"migrate_to_chat_id"` // optional
		RetryAfter      int   `json:"retry_after"`        // optional
	}
	type APIResponse struct {
		Ok          bool                `json:"ok"`
		Result      json.RawMessage     `json:"result"`
		ErrorCode   int                 `json:"error_code"`
		Description string              `json:"description"`
		Parameters  *ResponseParameters `json:"parameters"`
	}
	var resp APIResponse
	err = json.Unmarshal([]byte(body), &resp)
	if err != nil {
		fmt.Println("error:", err)
		return errors.New("Failed to parse response")
	}
	if !resp.Ok {
		return errors.New(resp.Description)
	}

	return nil
}

// SendNotify sends mail notify if IP is changed
func SendMailNotify(configuration *Settings, domain, currentIP string) error {
	if !configuration.Notify.Mail.Enabled {
		return nil
	}
	log.Print("Sending notification to:", configuration.Notify.Mail.SendTo)
	m := gomail.NewMessage()

	m.SetHeader("From", configuration.Notify.Mail.SMTPUsername)
	m.SetHeader("To", configuration.Notify.Mail.SendTo)
	m.SetHeader("Subject", "GoDNS Notification")
	log.Println("currentIP:", currentIP)
	log.Println("domain:", domain)
	m.SetBody("text/html", buildTemplate(currentIP, domain, mailTemplate))

	d := gomail.NewPlainDialer(configuration.Notify.Mail.SMTPServer, configuration.Notify.Mail.SMTPPort, configuration.Notify.Mail.SMTPUsername, configuration.Notify.Mail.SMTPPassword)

	// Send the email config by sendlist	.
	if err := d.DialAndSend(m); err != nil {
		return err
	}
	return nil
}

// SendNotify sends notify if IP is changed
func SendNotify(configuration *Settings, domain, currentIP string) error {
	err := SendTelegramNotify(configuration, domain, currentIP)
	if err != nil {
		log.Println("Send telegram notification with error:", err.Error())
	}
	err = SendMailNotify(configuration, domain, currentIP)
	if err != nil {
		log.Println("Send email notification with error:", err.Error())
	}
	return nil
}

func buildTemplate(currentIP, domain string, tplsrc string) string {
	t := template.New("notification template")
	if _, err := t.Parse(tplsrc); err != nil {
		log.Println("Failed to parse template")
		return ""
	}

	data := struct {
		CurrentIP string
		Domain    string
	}{
		currentIP,
		domain,
	}

	var tpl bytes.Buffer
	if err := t.Execute(&tpl, data); err != nil {
		log.Println(err.Error())
		return ""
	}

	return tpl.String()
}
<|endoftext|>