<commit_before>package main

import (
	"fmt"
	"os"

	"github.com/containers/image/signature"
	"github.com/containers/skopeo/version"
	"github.com/containers/storage/pkg/reexec"
	"github.com/sirupsen/logrus"
	"github.com/urfave/cli"
)

// gitCommit will be the hash that the binary was built from
// and will be populated by the Makefile
var gitCommit = ""

type globalOptions struct {
}

// createApp returns a cli.App to be run or tested.
func createApp() *cli.App {
	opts := globalOptions{}

	app := cli.NewApp()
	app.EnableBashCompletion = true
	app.Name = "skopeo"
	if gitCommit != "" {
		app.Version = fmt.Sprintf("%s commit: %s", version.Version, gitCommit)
	} else {
		app.Version = version.Version
	}
	app.Usage = "Various operations with container images and container image registries"
	app.Flags = []cli.Flag{
		cli.BoolFlag{
			Name:  "debug",
			Usage: "enable debug output",
		},
		cli.BoolTFlag{
			Name:   "tls-verify",
			Usage:  "require HTTPS and verify certificates when talking to container registries (defaults to true)",
			Hidden: true,
		},
		cli.StringFlag{
			Name:  "policy",
			Value: "",
			Usage: "Path to a trust policy file",
		},
		cli.BoolFlag{
			Name:  "insecure-policy",
			Usage: "run the tool without any policy check",
		},
		cli.StringFlag{
			Name:  "registries.d",
			Value: "",
			Usage: "use registry configuration files in `DIR` (e.g. for container signature storage)",
		},
		cli.StringFlag{
			Name:  "override-arch",
			Value: "",
			Usage: "use `ARCH` instead of the architecture of the machine for choosing images",
		},
		cli.StringFlag{
			Name:  "override-os",
			Value: "",
			Usage: "use `OS` instead of the running OS for choosing images",
		},
		cli.DurationFlag{
			Name:  "command-timeout",
			Usage: "timeout for the command execution",
		},
	}
	app.Before = opts.before
	app.Commands = []cli.Command{
		copyCmd(),
		inspectCmd(),
		layersCmd(),
		deleteCmd(),
		manifestDigestCmd(),
		standaloneSignCmd(),
		standaloneVerifyCmd(),
		untrustedSignatureDumpCmd(),
	}
	return app
}

// before is run by the cli package for any command, before running the command-specific handler.
func (opts *globalOptions) before(c *cli.Context) error {
	if c.GlobalBool("debug") {
		logrus.SetLevel(logrus.DebugLevel)
	}
	if c.GlobalIsSet("tls-verify") {
		logrus.Warn("'--tls-verify' is deprecated, please set this on the specific subcommand")
	}
	return nil
}

func main() {
	if reexec.Init() {
		return
	}
	app := createApp()
	if err := app.Run(os.Args); err != nil {
		logrus.Fatal(err)
	}
}

// getPolicyContext handles the global "policy" flag.
func getPolicyContext(c *cli.Context) (*signature.PolicyContext, error) {
	policyPath := c.GlobalString("policy")
	var policy *signature.Policy // This could be cached across calls, if we had an application context.
	var err error
	if c.GlobalBool("insecure-policy") {
		policy = &signature.Policy{Default: []signature.PolicyRequirement{signature.NewPRInsecureAcceptAnything()}}
	} else if policyPath == "" {
		policy, err = signature.DefaultPolicy(nil)
	} else {
		policy, err = signature.NewPolicyFromFile(policyPath)
	}
	if err != nil {
		return nil, err
	}
	return signature.NewPolicyContext(policy)
}
<commit_msg>Use globalOptions for the debug flag<commit_after>package main

import (
	"fmt"
	"os"

	"github.com/containers/image/signature"
	"github.com/containers/skopeo/version"
	"github.com/containers/storage/pkg/reexec"
	"github.com/sirupsen/logrus"
	"github.com/urfave/cli"
)

// gitCommit will be the hash that the binary was built from
// and will be populated by the Makefile
var gitCommit = ""

type globalOptions struct {
	debug bool // Enable debug output
}

// createApp returns a cli.App to be run or tested.
func createApp() *cli.App {
	opts := globalOptions{}

	app := cli.NewApp()
	app.EnableBashCompletion = true
	app.Name = "skopeo"
	if gitCommit != "" {
		app.Version = fmt.Sprintf("%s commit: %s", version.Version, gitCommit)
	} else {
		app.Version = version.Version
	}
	app.Usage = "Various operations with container images and container image registries"
	app.Flags = []cli.Flag{
		cli.BoolFlag{
			Name:        "debug",
			Usage:       "enable debug output",
			Destination: &opts.debug,
		},
		cli.BoolTFlag{
			Name:   "tls-verify",
			Usage:  "require HTTPS and verify certificates when talking to container registries (defaults to true)",
			Hidden: true,
		},
		cli.StringFlag{
			Name:  "policy",
			Value: "",
			Usage: "Path to a trust policy file",
		},
		cli.BoolFlag{
			Name:  "insecure-policy",
			Usage: "run the tool without any policy check",
		},
		cli.StringFlag{
			Name:  "registries.d",
			Value: "",
			Usage: "use registry configuration files in `DIR` (e.g. for container signature storage)",
		},
		cli.StringFlag{
			Name:  "override-arch",
			Value: "",
			Usage: "use `ARCH` instead of the architecture of the machine for choosing images",
		},
		cli.StringFlag{
			Name:  "override-os",
			Value: "",
			Usage: "use `OS` instead of the running OS for choosing images",
		},
		cli.DurationFlag{
			Name:  "command-timeout",
			Usage: "timeout for the command execution",
		},
	}
	app.Before = opts.before
	app.Commands = []cli.Command{
		copyCmd(),
		inspectCmd(),
		layersCmd(),
		deleteCmd(),
		manifestDigestCmd(),
		standaloneSignCmd(),
		standaloneVerifyCmd(),
		untrustedSignatureDumpCmd(),
	}
	return app
}

// before is run by the cli package for any command, before running the command-specific handler.
func (opts *globalOptions) before(c *cli.Context) error {
	if opts.debug {
		logrus.SetLevel(logrus.DebugLevel)
	}
	if c.GlobalIsSet("tls-verify") {
		logrus.Warn("'--tls-verify' is deprecated, please set this on the specific subcommand")
	}
	return nil
}

func main() {
	if reexec.Init() {
		return
	}
	app := createApp()
	if err := app.Run(os.Args); err != nil {
		logrus.Fatal(err)
	}
}

// getPolicyContext handles the global "policy" flag.
func getPolicyContext(c *cli.Context) (*signature.PolicyContext, error) {
	policyPath := c.GlobalString("policy")
	var policy *signature.Policy // This could be cached across calls, if we had an application context.
	var err error
	if c.GlobalBool("insecure-policy") {
		policy = &signature.Policy{Default: []signature.PolicyRequirement{signature.NewPRInsecureAcceptAnything()}}
	} else if policyPath == "" {
		policy, err = signature.DefaultPolicy(nil)
	} else {
		policy, err = signature.NewPolicyFromFile(policyPath)
	}
	if err != nil {
		return nil, err
	}
	return signature.NewPolicyContext(policy)
}
<|endoftext|>


<commit_before>package main

import (
	"log"
	"net/http"
	_ "net/http/pprof"
	"os"
	"os/signal"
	"syscall"

	"github.com/antihax/evedata/internal/nsqhelper"
	"github.com/antihax/evedata/internal/sqlhelper"
	"github.com/antihax/evedata/services/tailor"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	backblaze "gopkg.in/kothar/go-backblaze.v0"
)

func main() {
	log.SetFlags(log.LstdFlags | log.Lshortfile)
	log.SetPrefix("evedata tailor: ")

	db := sqlhelper.NewDatabase()

	b2, err := backblaze.NewB2(backblaze.Credentials{
		AccountID:      os.Getenv("B2_ACCOUNTID"),
		ApplicationKey: os.Getenv("B2_APPLICATION_KEY"),
	})
	if err != nil {
		log.Fatal(err)
	}

	// Make a new service and send it into the background.
	tailor := tailor.NewTailor(
		db,
		b2,
		nsqhelper.Prod,
	)

	defer tailor.Close()

	// Run metrics
	http.Handle("/metrics", promhttp.Handler())

	go log.Fatalln(http.ListenAndServe(":3000", nil))

	// Handle SIGINT and SIGTERM.
	ch := make(chan os.Signal)
	signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
	log.Println(<-ch)
}
<commit_msg>revert<commit_after>package main

import (
	"log"
	"net/http"
	_ "net/http/pprof"
	"os"
	"os/signal"
	"syscall"

	"github.com/antihax/evedata/internal/nsqhelper"
	"github.com/antihax/evedata/internal/sqlhelper"
	"github.com/antihax/evedata/services/tailor"
	backblaze "gopkg.in/kothar/go-backblaze.v0"

	"github.com/prometheus/client_golang/prometheus/promhttp"
)

func main() {
	log.SetFlags(log.LstdFlags | log.Lshortfile)
	log.SetPrefix("evedata tailor: ")

	db := sqlhelper.NewDatabase()

	/*
		b2, err := backblaze.NewB2WithHTTPClient(backblaze.Credentials{
			AccountID:      os.Getenv("B2_ACCOUNTID"),
			ApplicationKey: os.Getenv("B2_APPLICATION_KEY"),
		}, &http.Client{
			Transport: &tailor.ApiTransport{
				Next: &http.Transport{
					MaxIdleConns: 200,
					DialContext: (&net.Dialer{
						Timeout:   300 * time.Second,
						KeepAlive: 5 * 60 * time.Second,
						DualStack: true,
					}).DialContext,
					IdleConnTimeout:       5 * 60 * time.Second,
					TLSHandshakeTimeout:   10 * time.Second,
					ResponseHeaderTimeout: 60 * time.Second,
					ExpectContinueTimeout: 0,
					MaxIdleConnsPerHost:   20,
				},
			}})
			b2.Debug = true
	*/
	b2, err := backblaze.NewB2(backblaze.Credentials{
		AccountID:      os.Getenv("B2_ACCOUNTID"),
		ApplicationKey: os.Getenv("B2_APPLICATION_KEY"),
	})
	if err != nil {
		log.Fatal(err)
	}

	// Make a new service and send it into the background.
	tailor := tailor.NewTailor(
		db,
		b2,
		nsqhelper.Prod,
	)

	defer tailor.Close()

	// Run metrics
	http.Handle("/metrics", promhttp.Handler())

	go log.Fatalln(http.ListenAndServe(":3000", nil))

	// Handle SIGINT and SIGTERM.
	ch := make(chan os.Signal)
	signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
	log.Println(<-ch)
}
<|endoftext|>


<commit_before>/*
 * Minio Cloud Storage, (C) 2015, 2016, 2017 Minio, 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 cmd

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"time"

	"github.com/fatih/color"
	"github.com/minio/cli"
	"github.com/minio/mc/pkg/console"
)

// Check for new software updates.
var updateCmd = cli.Command{
	Name:   "update",
	Usage:  "Check for a new software update.",
	Action: mainUpdate,
	Flags: []cli.Flag{
		cli.BoolFlag{
			Name:  "quiet",
			Usage: "Disable any update messages.",
		},
	},
	CustomHelpTemplate: `Name:
   {{.HelpName}} - {{.Usage}}

USAGE:
   {{.HelpName}}{{if .VisibleFlags}} [FLAGS]{{end}}
{{if .VisibleFlags}}
FLAGS:
  {{range .VisibleFlags}}{{.}}
  {{end}}{{end}}
EXIT STATUS:
   0 - You are already running the most recent version.
   1 - New update is available.
  -1 - Error in getting update information.

EXAMPLES:
   1. Check if there is a new update available:
       $ {{.HelpName}}
`,
}

const (
	minioReleaseTagTimeLayout = "2006-01-02T15-04-05Z"
	minioReleaseURL           = "https://dl.minio.io/server/minio/release/" + runtime.GOOS + "-" + runtime.GOARCH + "/"
)

func getCurrentReleaseTime(minioVersion, minioBinaryPath string) (releaseTime time.Time, err error) {
	if releaseTime, err = time.Parse(time.RFC3339, minioVersion); err == nil {
		return releaseTime, err
	}

	if !filepath.IsAbs(minioBinaryPath) {
		// Make sure to look for the absolute path of the binary.
		minioBinaryPath, err = exec.LookPath(minioBinaryPath)
		if err != nil {
			return releaseTime, err
		}
	}

	// Looks like version is minio non-standard, we use minio binary's ModTime as release time.
	fi, err := os.Stat(minioBinaryPath)
	if err != nil {
		err = fmt.Errorf("Unable to get ModTime of %s. %s", minioBinaryPath, err)
	} else {
		releaseTime = fi.ModTime().UTC()
	}

	return releaseTime, err
}

// GetCurrentReleaseTime - returns this process's release time.  If it is official minio version,
// parsed version is returned else minio binary's mod time is returned.
func GetCurrentReleaseTime() (releaseTime time.Time, err error) {
	return getCurrentReleaseTime(Version, os.Args[0])
}

func isDocker(cgroupFile string) (bool, error) {
	cgroup, err := ioutil.ReadFile(cgroupFile)
	if os.IsNotExist(err) {
		err = nil
	}

	return bytes.Contains(cgroup, []byte("docker")), err
}

// IsDocker - returns if the environment is docker or not.
func IsDocker() bool {
	found, err := isDocker("/proc/self/cgroup")
	if err != nil {
		console.Fatalf("Error in docker check: %s", err)
	}

	return found
}

func isSourceBuild(minioVersion string) bool {
	_, err := time.Parse(time.RFC3339, minioVersion)
	return err != nil
}

// IsSourceBuild - returns if this binary is made from source or not.
func IsSourceBuild() bool {
	return isSourceBuild(Version)
}

// DO NOT CHANGE USER AGENT STYLE.
// The style should be
//   Minio (<OS>; <ARCH>[; docker][; source])  Minio/<VERSION> Minio/<RELEASE-TAG> Minio/<COMMIT-ID>
//
// For any change here should be discussed by openning an issue at https://github.com/minio/minio/issues.
func getUserAgent() string {
	userAgent := "Minio (" + runtime.GOOS + "; " + runtime.GOARCH
	if IsDocker() {
		userAgent += "; docker"
	}
	if IsSourceBuild() {
		userAgent += "; source"
	}
	userAgent += ") " + " Minio/" + Version + " Minio/" + ReleaseTag + " Minio/" + CommitID

	return userAgent
}

func downloadReleaseData(releaseChecksumURL string, timeout time.Duration) (data string, err error) {
	req, err := http.NewRequest("GET", releaseChecksumURL, nil)
	if err != nil {
		return data, err
	}
	req.Header.Set("User-Agent", getUserAgent())

	client := &http.Client{
		Timeout: timeout,
	}

	resp, err := client.Do(req)
	if err != nil {
		return data, err
	}
	if resp == nil {
		return data, fmt.Errorf("No response from server to download URL %s", releaseChecksumURL)
	}

	if resp.StatusCode != http.StatusOK {
		return data, fmt.Errorf("Error downloading URL %s. Response: %v", releaseChecksumURL, resp.Status)
	}

	dataBytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return data, fmt.Errorf("Error reading response. %s", err)
	}

	data = string(dataBytes)
	return data, err
}

// DownloadReleaseData - downloads release data from minio official server.
func DownloadReleaseData(timeout time.Duration) (data string, err error) {
	return downloadReleaseData(minioReleaseURL+"minio.shasum", timeout)
}

func parseReleaseData(data string) (releaseTime time.Time, err error) {
	fields := strings.Fields(data)
	if len(fields) != 2 {
		err = fmt.Errorf("Unknown release data `%s`", data)
		return releaseTime, err
	}

	releaseInfo := fields[1]
	if fields = strings.Split(releaseInfo, "."); len(fields) != 3 {
		err = fmt.Errorf("Unknown release information `%s`", releaseInfo)
		return releaseTime, err
	}

	if !(fields[0] == "minio" && fields[1] == "RELEASE") {
		err = fmt.Errorf("Unknown release '%s'", releaseInfo)
		return releaseTime, err
	}

	releaseTime, err = time.Parse(minioReleaseTagTimeLayout, fields[2])
	if err != nil {
		err = fmt.Errorf("Unknown release time format. %s", err)
	}

	return releaseTime, err
}

func getLatestReleaseTime(timeout time.Duration) (releaseTime time.Time, err error) {
	data, err := DownloadReleaseData(timeout)
	if err != nil {
		return releaseTime, err
	}

	return parseReleaseData(data)
}

func getDownloadURL() (downloadURL string) {
	if IsDocker() {
		return "docker pull minio/minio"
	}

	if runtime.GOOS == "windows" {
		return minioReleaseURL + "minio.exe"
	}

	return minioReleaseURL + "minio"
}

func getUpdateInfo(timeout time.Duration) (older time.Duration, downloadURL string, err error) {
	currentReleaseTime, err := GetCurrentReleaseTime()
	if err != nil {
		return older, downloadURL, err
	}

	latestReleaseTime, err := getLatestReleaseTime(timeout)
	if err != nil {
		return older, downloadURL, err
	}

	if latestReleaseTime.After(currentReleaseTime) {
		older = latestReleaseTime.Sub(currentReleaseTime)
		downloadURL = getDownloadURL()
	}

	return older, downloadURL, nil
}

func mainUpdate(ctx *cli.Context) {
	if len(ctx.Args()) != 0 {
		cli.ShowCommandHelpAndExit(ctx, "update", -1)
	}

	quiet := ctx.Bool("quiet") || ctx.GlobalBool("quiet")
	quietPrintln := func(args ...interface{}) {
		if !quiet {
			console.Println(args...)
		}
	}

	older, downloadURL, err := getUpdateInfo(10 * time.Second)
	if err != nil {
		quietPrintln(err)
		os.Exit(-1)
	}

	if older != time.Duration(0) {
		quietPrintln(colorizeUpdateMessage(downloadURL, older))
		os.Exit(1)
	}

	colorSprintf := color.New(color.FgGreen, color.Bold).SprintfFunc()
	quietPrintln(colorSprintf("You are already running the most recent version of ‘minio’."))
	os.Exit(0)
}
<commit_msg>Close client connection after checking for release update (#3820)<commit_after>/*
 * Minio Cloud Storage, (C) 2015, 2016, 2017 Minio, 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 cmd

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"time"

	"github.com/fatih/color"
	"github.com/minio/cli"
	"github.com/minio/mc/pkg/console"
)

// Check for new software updates.
var updateCmd = cli.Command{
	Name:   "update",
	Usage:  "Check for a new software update.",
	Action: mainUpdate,
	Flags: []cli.Flag{
		cli.BoolFlag{
			Name:  "quiet",
			Usage: "Disable any update messages.",
		},
	},
	CustomHelpTemplate: `Name:
   {{.HelpName}} - {{.Usage}}

USAGE:
   {{.HelpName}}{{if .VisibleFlags}} [FLAGS]{{end}}
{{if .VisibleFlags}}
FLAGS:
  {{range .VisibleFlags}}{{.}}
  {{end}}{{end}}
EXIT STATUS:
   0 - You are already running the most recent version.
   1 - New update is available.
  -1 - Error in getting update information.

EXAMPLES:
   1. Check if there is a new update available:
       $ {{.HelpName}}
`,
}

const (
	minioReleaseTagTimeLayout = "2006-01-02T15-04-05Z"
	minioReleaseURL           = "https://dl.minio.io/server/minio/release/" + runtime.GOOS + "-" + runtime.GOARCH + "/"
)

func getCurrentReleaseTime(minioVersion, minioBinaryPath string) (releaseTime time.Time, err error) {
	if releaseTime, err = time.Parse(time.RFC3339, minioVersion); err == nil {
		return releaseTime, err
	}

	if !filepath.IsAbs(minioBinaryPath) {
		// Make sure to look for the absolute path of the binary.
		minioBinaryPath, err = exec.LookPath(minioBinaryPath)
		if err != nil {
			return releaseTime, err
		}
	}

	// Looks like version is minio non-standard, we use minio binary's ModTime as release time.
	fi, err := os.Stat(minioBinaryPath)
	if err != nil {
		err = fmt.Errorf("Unable to get ModTime of %s. %s", minioBinaryPath, err)
	} else {
		releaseTime = fi.ModTime().UTC()
	}

	return releaseTime, err
}

// GetCurrentReleaseTime - returns this process's release time.  If it is official minio version,
// parsed version is returned else minio binary's mod time is returned.
func GetCurrentReleaseTime() (releaseTime time.Time, err error) {
	return getCurrentReleaseTime(Version, os.Args[0])
}

func isDocker(cgroupFile string) (bool, error) {
	cgroup, err := ioutil.ReadFile(cgroupFile)
	if os.IsNotExist(err) {
		err = nil
	}

	return bytes.Contains(cgroup, []byte("docker")), err
}

// IsDocker - returns if the environment is docker or not.
func IsDocker() bool {
	found, err := isDocker("/proc/self/cgroup")
	if err != nil {
		console.Fatalf("Error in docker check: %s", err)
	}

	return found
}

func isSourceBuild(minioVersion string) bool {
	_, err := time.Parse(time.RFC3339, minioVersion)
	return err != nil
}

// IsSourceBuild - returns if this binary is made from source or not.
func IsSourceBuild() bool {
	return isSourceBuild(Version)
}

// DO NOT CHANGE USER AGENT STYLE.
// The style should be
//   Minio (<OS>; <ARCH>[; docker][; source])  Minio/<VERSION> Minio/<RELEASE-TAG> Minio/<COMMIT-ID>
//
// For any change here should be discussed by openning an issue at https://github.com/minio/minio/issues.
func getUserAgent() string {
	userAgent := "Minio (" + runtime.GOOS + "; " + runtime.GOARCH
	if IsDocker() {
		userAgent += "; docker"
	}
	if IsSourceBuild() {
		userAgent += "; source"
	}
	userAgent += ") " + " Minio/" + Version + " Minio/" + ReleaseTag + " Minio/" + CommitID

	return userAgent
}

func downloadReleaseData(releaseChecksumURL string, timeout time.Duration) (data string, err error) {
	req, err := http.NewRequest("GET", releaseChecksumURL, nil)
	if err != nil {
		return data, err
	}
	req.Header.Set("User-Agent", getUserAgent())

	client := &http.Client{
		Timeout: timeout,
		Transport: &http.Transport{
			// need to close connection after usage.
			DisableKeepAlives: true,
		},
	}

	resp, err := client.Do(req)
	if err != nil {
		return data, err
	}
	if resp == nil {
		return data, fmt.Errorf("No response from server to download URL %s", releaseChecksumURL)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return data, fmt.Errorf("Error downloading URL %s. Response: %v", releaseChecksumURL, resp.Status)
	}

	dataBytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return data, fmt.Errorf("Error reading response. %s", err)
	}

	data = string(dataBytes)
	return data, err
}

// DownloadReleaseData - downloads release data from minio official server.
func DownloadReleaseData(timeout time.Duration) (data string, err error) {
	return downloadReleaseData(minioReleaseURL+"minio.shasum", timeout)
}

func parseReleaseData(data string) (releaseTime time.Time, err error) {
	fields := strings.Fields(data)
	if len(fields) != 2 {
		err = fmt.Errorf("Unknown release data `%s`", data)
		return releaseTime, err
	}

	releaseInfo := fields[1]
	if fields = strings.Split(releaseInfo, "."); len(fields) != 3 {
		err = fmt.Errorf("Unknown release information `%s`", releaseInfo)
		return releaseTime, err
	}

	if !(fields[0] == "minio" && fields[1] == "RELEASE") {
		err = fmt.Errorf("Unknown release '%s'", releaseInfo)
		return releaseTime, err
	}

	releaseTime, err = time.Parse(minioReleaseTagTimeLayout, fields[2])
	if err != nil {
		err = fmt.Errorf("Unknown release time format. %s", err)
	}

	return releaseTime, err
}

func getLatestReleaseTime(timeout time.Duration) (releaseTime time.Time, err error) {
	data, err := DownloadReleaseData(timeout)
	if err != nil {
		return releaseTime, err
	}

	return parseReleaseData(data)
}

func getDownloadURL() (downloadURL string) {
	if IsDocker() {
		return "docker pull minio/minio"
	}

	if runtime.GOOS == "windows" {
		return minioReleaseURL + "minio.exe"
	}

	return minioReleaseURL + "minio"
}

func getUpdateInfo(timeout time.Duration) (older time.Duration, downloadURL string, err error) {
	currentReleaseTime, err := GetCurrentReleaseTime()
	if err != nil {
		return older, downloadURL, err
	}

	latestReleaseTime, err := getLatestReleaseTime(timeout)
	if err != nil {
		return older, downloadURL, err
	}

	if latestReleaseTime.After(currentReleaseTime) {
		older = latestReleaseTime.Sub(currentReleaseTime)
		downloadURL = getDownloadURL()
	}

	return older, downloadURL, nil
}

func mainUpdate(ctx *cli.Context) {
	if len(ctx.Args()) != 0 {
		cli.ShowCommandHelpAndExit(ctx, "update", -1)
	}

	quiet := ctx.Bool("quiet") || ctx.GlobalBool("quiet")
	quietPrintln := func(args ...interface{}) {
		if !quiet {
			console.Println(args...)
		}
	}

	older, downloadURL, err := getUpdateInfo(10 * time.Second)
	if err != nil {
		quietPrintln(err)
		os.Exit(-1)
	}

	if older != time.Duration(0) {
		quietPrintln(colorizeUpdateMessage(downloadURL, older))
		os.Exit(1)
	}

	colorSprintf := color.New(color.FgGreen, color.Bold).SprintfFunc()
	quietPrintln(colorSprintf("You are already running the most recent version of ‘minio’."))
	os.Exit(0)
}
<|endoftext|>


<commit_before>// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Command worker runs the vuln worker server.
// It can also be used to perform actions from the command line
// by providing a sub-command.
package main

import (
	"bufio"
	"context"
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"strings"
	"text/tabwriter"

	"golang.org/x/exp/event"
	"golang.org/x/vuln/internal/gitrepo"
	"golang.org/x/vuln/internal/worker"
	"golang.org/x/vuln/internal/worker/log"
	"golang.org/x/vuln/internal/worker/store"
)

var (
	// Flags only for the command-line tool.
	localRepoPath = flag.String("local-cve-repo", "", "path to local repo, instead of cloning remote")
	force         = flag.Bool("force", false, "force an update to happen")
	limit         = flag.Int("limit", 0,
		"limit on number of things to list or issues to create (0 means unlimited)")
	githubTokenFile = flag.String("ghtokenfile", "",
		"path to file containing GitHub access token (for creating issues)")
	knownModuleFile = flag.String("known-module-file", "", "file with list of all known modules")
)

// Config for both the server and the command-line tool.
var cfg worker.Config

func init() {
	flag.StringVar(&cfg.Project, "project", os.Getenv("GOOGLE_CLOUD_PROJECT"), "project ID (required)")
	flag.StringVar(&cfg.Namespace, "namespace", os.Getenv("VULN_WORKER_NAMESPACE"), "Firestore namespace (required)")
	flag.BoolVar(&cfg.UseErrorReporting, "report-errors", os.Getenv("VULN_WORKER_REPORT_ERRORS") == "true",
		"use the error reporting API")
	flag.StringVar(&cfg.IssueRepo, "issue-repo", os.Getenv("VULN_WORKER_ISSUE_REPO"), "repo to create issues in")
}

const pkgsiteURL = "https://pkg.go.dev"

func main() {
	flag.Usage = func() {
		out := flag.CommandLine.Output()
		fmt.Fprintln(out, "usage:")
		fmt.Fprintln(out, "worker FLAGS")
		fmt.Fprintln(out, "  run as a server, listening at the PORT env var")
		fmt.Fprintln(out, "worker FLAGS SUBCOMMAND ...")
		fmt.Fprintln(out, "  run as a command-line tool, executing SUBCOMMAND")
		fmt.Fprintln(out, "  subcommands:")
		fmt.Fprintln(out, "    update COMMIT: perform an update operation")
		fmt.Fprintln(out, "    list-updates: display info about update operations")
		fmt.Fprintln(out, "    list-cves TRIAGE_STATE: display info about CVE records")
		fmt.Fprintln(out, "    create-issues: create issues for CVEs that need them")
		fmt.Fprintln(out, "flags:")
		flag.PrintDefaults()
	}

	flag.Parse()
	if *githubTokenFile != "" {
		data, err := ioutil.ReadFile(*githubTokenFile)
		if err != nil {
			die("%v", err)
		}
		cfg.GitHubAccessToken = strings.TrimSpace(string(data))
	}
	if err := cfg.Validate(); err != nil {
		dieWithUsage("%v", err)
	}

	ctx := log.WithLineLogger(context.Background())
	log.Info(ctx, "config",
		event.String("Project", cfg.Project),
		event.String("Namespace", cfg.Namespace),
		event.String("IssueRepo", cfg.IssueRepo))

	var err error
	cfg.Store, err = store.NewFireStore(ctx, cfg.Project, cfg.Namespace)
	if err != nil {
		die("firestore: %v", err)
	}
	if flag.NArg() > 0 {
		err = runCommandLine(ctx)
	} else {
		err = runServer(ctx)
	}
	if err != nil {
		dieWithUsage("%v", err)
	}
}

func runServer(ctx context.Context) error {
	if os.Getenv("PORT") == "" {
		return errors.New("need PORT")
	}
	if _, err := worker.NewServer(ctx, cfg); err != nil {
		return err
	}
	addr := ":" + os.Getenv("PORT")
	log.Infof(ctx, "Listening on addr %s", addr)
	return fmt.Errorf("listening: %v", http.ListenAndServe(addr, nil))
}

const timeFormat = "2006/01/02 15:04:05"

func runCommandLine(ctx context.Context) error {
	switch flag.Arg(0) {
	case "list-updates":
		return listUpdatesCommand(ctx)
	case "list-cves":
		return listCVEsCommand(ctx, flag.Arg(1))
	case "update":
		if flag.NArg() != 2 {
			return errors.New("usage: update COMMIT")
		}
		return updateCommand(ctx, flag.Arg(1))
	case "create-issues":
		return createIssuesCommand(ctx)
	default:
		return fmt.Errorf("unknown command: %q", flag.Arg(1))
	}
}

func listUpdatesCommand(ctx context.Context) error {
	recs, err := cfg.Store.ListCommitUpdateRecords(ctx, 0)
	if err != nil {
		return err
	}
	tw := tabwriter.NewWriter(os.Stdout, 1, 8, 2, ' ', 0)
	fmt.Fprintf(tw, "Start\tEnd\tCommit\tCVEs Processed\n")
	for i, r := range recs {
		if *limit > 0 && i >= *limit {
			break
		}
		endTime := "unfinished"
		if !r.EndedAt.IsZero() {
			endTime = r.EndedAt.Format(timeFormat)
		}
		fmt.Fprintf(tw, "%s\t%s\t%s\t%d/%d (added %d, modified %d)\n",
			r.StartedAt.Format(timeFormat),
			endTime,
			r.CommitHash,
			r.NumProcessed, r.NumTotal, r.NumAdded, r.NumModified)
	}
	return tw.Flush()
}

func listCVEsCommand(ctx context.Context, triageState string) error {
	ts := store.TriageState(triageState)
	if err := ts.Validate(); err != nil {
		return err
	}
	crs, err := cfg.Store.ListCVERecordsWithTriageState(ctx, ts)
	if err != nil {
		return err
	}
	tw := tabwriter.NewWriter(os.Stdout, 1, 8, 2, ' ', 0)
	fmt.Fprintf(tw, "ID\tCVEState\tCommit\tReason\tModule\tIssue\tIssue Created\n")
	for i, r := range crs {
		if *limit > 0 && i >= *limit {
			break
		}
		fmt.Fprintf(tw, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
			r.ID, r.CVEState, r.CommitHash, r.TriageStateReason, r.Module, r.IssueReference, worker.FormatTime(r.IssueCreatedAt))
	}
	return tw.Flush()
}

func updateCommand(ctx context.Context, commitHash string) error {
	repoPath := gitrepo.CVEListRepoURL
	if *localRepoPath != "" {
		repoPath = *localRepoPath
	}
	if *knownModuleFile != "" {
		if err := populateKnownModules(*knownModuleFile); err != nil {
			return err
		}
	}
	err := worker.UpdateCommit(ctx, repoPath, commitHash, cfg.Store, pkgsiteURL, *force)
	if cerr := new(worker.CheckUpdateError); errors.As(err, &cerr) {
		return fmt.Errorf("%w; use -force to override", cerr)
	}
	return err
}

func populateKnownModules(filename string) error {
	f, err := os.Open(filename)
	if err != nil {
		return err
	}
	defer f.Close()

	var mods []string
	scan := bufio.NewScanner(f)
	for scan.Scan() {
		line := strings.TrimSpace(scan.Text())
		if line == "" || line[0] == '#' {
			continue
		}
		mods = append(mods, line)
	}
	if err := scan.Err(); err != nil {
		return err
	}
	worker.SetKnownModules(mods)
	fmt.Printf("set %d known modules\n", len(mods))
	return nil
}

func createIssuesCommand(ctx context.Context) error {
	owner, repoName, err := worker.ParseGithubRepo(cfg.IssueRepo)
	if err != nil {
		return err
	}
	client := worker.NewGithubIssueClient(owner, repoName, cfg.GitHubAccessToken)
	return worker.CreateIssues(ctx, cfg.Store, client, *limit)
}

func die(format string, args ...interface{}) {
	fmt.Fprintf(os.Stderr, format, args...)
	fmt.Fprintln(os.Stderr)
	os.Exit(1)
}

func dieWithUsage(format string, args ...interface{}) {
	fmt.Fprintf(os.Stderr, format, args...)
	fmt.Fprintln(os.Stderr)
	flag.Usage()
	os.Exit(1)
}
<commit_msg>cmd/worker: add show command<commit_after>// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Command worker runs the vuln worker server.
// It can also be used to perform actions from the command line
// by providing a sub-command.
package main

import (
	"bufio"
	"context"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"strings"
	"text/tabwriter"

	"golang.org/x/exp/event"
	"golang.org/x/vuln/internal/gitrepo"
	"golang.org/x/vuln/internal/worker"
	"golang.org/x/vuln/internal/worker/log"
	"golang.org/x/vuln/internal/worker/store"
)

var (
	// Flags only for the command-line tool.
	localRepoPath = flag.String("local-cve-repo", "", "path to local repo, instead of cloning remote")
	force         = flag.Bool("force", false, "force an update to happen")
	limit         = flag.Int("limit", 0,
		"limit on number of things to list or issues to create (0 means unlimited)")
	githubTokenFile = flag.String("ghtokenfile", "",
		"path to file containing GitHub access token (for creating issues)")
	knownModuleFile = flag.String("known-module-file", "", "file with list of all known modules")
)

// Config for both the server and the command-line tool.
var cfg worker.Config

func init() {
	flag.StringVar(&cfg.Project, "project", os.Getenv("GOOGLE_CLOUD_PROJECT"), "project ID (required)")
	flag.StringVar(&cfg.Namespace, "namespace", os.Getenv("VULN_WORKER_NAMESPACE"), "Firestore namespace (required)")
	flag.BoolVar(&cfg.UseErrorReporting, "report-errors", os.Getenv("VULN_WORKER_REPORT_ERRORS") == "true",
		"use the error reporting API")
	flag.StringVar(&cfg.IssueRepo, "issue-repo", os.Getenv("VULN_WORKER_ISSUE_REPO"), "repo to create issues in")
}

const pkgsiteURL = "https://pkg.go.dev"

func main() {
	flag.Usage = func() {
		out := flag.CommandLine.Output()
		fmt.Fprintln(out, "usage:")
		fmt.Fprintln(out, "worker FLAGS")
		fmt.Fprintln(out, "  run as a server, listening at the PORT env var")
		fmt.Fprintln(out, "worker FLAGS SUBCOMMAND ...")
		fmt.Fprintln(out, "  run as a command-line tool, executing SUBCOMMAND")
		fmt.Fprintln(out, "  subcommands:")
		fmt.Fprintln(out, "    update COMMIT: perform an update operation")
		fmt.Fprintln(out, "    list-updates: display info about update operations")
		fmt.Fprintln(out, "    list-cves TRIAGE_STATE: display info about CVE records")
		fmt.Fprintln(out, "    create-issues: create issues for CVEs that need them")
		fmt.Fprintln(out, "    show ID1 ID2 ...: display CVE records")
		fmt.Fprintln(out, "flags:")
		flag.PrintDefaults()
	}

	flag.Parse()
	if *githubTokenFile != "" {
		data, err := ioutil.ReadFile(*githubTokenFile)
		if err != nil {
			die("%v", err)
		}
		cfg.GitHubAccessToken = strings.TrimSpace(string(data))
	}
	if err := cfg.Validate(); err != nil {
		dieWithUsage("%v", err)
	}

	ctx := log.WithLineLogger(context.Background())
	log.Info(ctx, "config",
		event.String("Project", cfg.Project),
		event.String("Namespace", cfg.Namespace),
		event.String("IssueRepo", cfg.IssueRepo))

	var err error
	cfg.Store, err = store.NewFireStore(ctx, cfg.Project, cfg.Namespace)
	if err != nil {
		die("firestore: %v", err)
	}
	if flag.NArg() > 0 {
		err = runCommandLine(ctx)
	} else {
		err = runServer(ctx)
	}
	if err != nil {
		dieWithUsage("%v", err)
	}
}

func runServer(ctx context.Context) error {
	if os.Getenv("PORT") == "" {
		return errors.New("need PORT")
	}
	if _, err := worker.NewServer(ctx, cfg); err != nil {
		return err
	}
	addr := ":" + os.Getenv("PORT")
	log.Infof(ctx, "Listening on addr %s", addr)
	return fmt.Errorf("listening: %v", http.ListenAndServe(addr, nil))
}

const timeFormat = "2006/01/02 15:04:05"

func runCommandLine(ctx context.Context) error {
	switch flag.Arg(0) {
	case "list-updates":
		return listUpdatesCommand(ctx)
	case "list-cves":
		return listCVEsCommand(ctx, flag.Arg(1))
	case "update":
		if flag.NArg() != 2 {
			return errors.New("usage: update COMMIT")
		}
		return updateCommand(ctx, flag.Arg(1))
	case "create-issues":
		return createIssuesCommand(ctx)
	case "show":
		return showCommand(ctx, flag.Args()[1:])
	default:
		return fmt.Errorf("unknown command: %q", flag.Arg(1))
	}
}

func listUpdatesCommand(ctx context.Context) error {
	recs, err := cfg.Store.ListCommitUpdateRecords(ctx, 0)
	if err != nil {
		return err
	}
	tw := tabwriter.NewWriter(os.Stdout, 1, 8, 2, ' ', 0)
	fmt.Fprintf(tw, "Start\tEnd\tCommit\tCVEs Processed\n")
	for i, r := range recs {
		if *limit > 0 && i >= *limit {
			break
		}
		endTime := "unfinished"
		if !r.EndedAt.IsZero() {
			endTime = r.EndedAt.Format(timeFormat)
		}
		fmt.Fprintf(tw, "%s\t%s\t%s\t%d/%d (added %d, modified %d)\n",
			r.StartedAt.Format(timeFormat),
			endTime,
			r.CommitHash,
			r.NumProcessed, r.NumTotal, r.NumAdded, r.NumModified)
	}
	return tw.Flush()
}

func listCVEsCommand(ctx context.Context, triageState string) error {
	ts := store.TriageState(triageState)
	if err := ts.Validate(); err != nil {
		return err
	}
	crs, err := cfg.Store.ListCVERecordsWithTriageState(ctx, ts)
	if err != nil {
		return err
	}
	tw := tabwriter.NewWriter(os.Stdout, 1, 8, 2, ' ', 0)
	fmt.Fprintf(tw, "ID\tCVEState\tCommit\tReason\tModule\tIssue\tIssue Created\n")
	for i, r := range crs {
		if *limit > 0 && i >= *limit {
			break
		}
		fmt.Fprintf(tw, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
			r.ID, r.CVEState, r.CommitHash, r.TriageStateReason, r.Module, r.IssueReference, worker.FormatTime(r.IssueCreatedAt))
	}
	return tw.Flush()
}

func updateCommand(ctx context.Context, commitHash string) error {
	repoPath := gitrepo.CVEListRepoURL
	if *localRepoPath != "" {
		repoPath = *localRepoPath
	}
	if *knownModuleFile != "" {
		if err := populateKnownModules(*knownModuleFile); err != nil {
			return err
		}
	}
	err := worker.UpdateCommit(ctx, repoPath, commitHash, cfg.Store, pkgsiteURL, *force)
	if cerr := new(worker.CheckUpdateError); errors.As(err, &cerr) {
		return fmt.Errorf("%w; use -force to override", cerr)
	}
	return err
}

func populateKnownModules(filename string) error {
	f, err := os.Open(filename)
	if err != nil {
		return err
	}
	defer f.Close()

	var mods []string
	scan := bufio.NewScanner(f)
	for scan.Scan() {
		line := strings.TrimSpace(scan.Text())
		if line == "" || line[0] == '#' {
			continue
		}
		mods = append(mods, line)
	}
	if err := scan.Err(); err != nil {
		return err
	}
	worker.SetKnownModules(mods)
	fmt.Printf("set %d known modules\n", len(mods))
	return nil
}

func createIssuesCommand(ctx context.Context) error {
	owner, repoName, err := worker.ParseGithubRepo(cfg.IssueRepo)
	if err != nil {
		return err
	}
	client := worker.NewGithubIssueClient(owner, repoName, cfg.GitHubAccessToken)
	return worker.CreateIssues(ctx, cfg.Store, client, *limit)
}

func showCommand(ctx context.Context, ids []string) error {
	for _, id := range ids {
		r, err := cfg.Store.GetCVERecord(ctx, id)
		if err != nil {
			return err
		}
		if r == nil {
			fmt.Printf("%s not found\n", id)
		} else {
			// Display as JSON because it's an easy way to get nice formatting.
			j, err := json.MarshalIndent(r, "", "\t")
			if err != nil {
				return err
			}
			fmt.Printf("%s\n", j)
		}
	}
	return nil
}

func die(format string, args ...interface{}) {
	fmt.Fprintf(os.Stderr, format, args...)
	fmt.Fprintln(os.Stderr)
	os.Exit(1)
}

func dieWithUsage(format string, args ...interface{}) {
	fmt.Fprintf(os.Stderr, format, args...)
	fmt.Fprintln(os.Stderr)
	flag.Usage()
	os.Exit(1)
}
<|endoftext|>


<commit_before>package le

import (
	"bytes"
	"fmt"
	"net"
	"os"
	"sync"
	"time"
)

const (
	LOGENTRIES_ADDRESS = "data.logentries.com:10000"
)

type Writer struct {
	Token     string
	conn      net.Conn
	channel   chan []byte
	waitGroup sync.WaitGroup
}

// New returns a new Writer with a given Logentries token and buffer size. If
// the token is invalid, Logentries will ignore all submitted logs. The buffer
// size is the maximum number of writes that will be queued for sending before
// the Writer begins rejecting new writes.
func New(token string, buffer int) (w *Writer, err error) {
	w = &Writer{
		Token:   token,
		channel: make(chan []byte, buffer),
	}

	if err = w.connect(); err != nil {
		return nil, err
	}

	w.start()

	return w, nil
}

// Write queues lines of text for sending to Logentries. It returns an error
// only if the write buffer is full.
func (w *Writer) Write(p []byte) (n int, err error) {
	if len(w.channel) >= cap(w.channel) {
		return 0, fmt.Errorf("Buffer is full")
	}

	buf := make([]byte, len(p))
	copy(buf, p)
	w.channel <- buf
	w.waitGroup.Add(1)

	return len(p), nil
}

// Wait will block until all queued writes have been sent to Logentries.
func (w *Writer) Wait() {
	w.waitGroup.Wait()
}

func (w *Writer) start() {
	go func() {
		for {
			w.write(<-w.channel)
			w.waitGroup.Done()
		}
	}()
}

func (w *Writer) connect() (err error) {
	if w.conn != nil {
		w.conn.Close()
		w.conn = nil
	}

	w.conn, err = net.DialTimeout("tcp", LOGENTRIES_ADDRESS, time.Second)
	return err
}

func (w *Writer) write(lines []byte) {
	if w.conn == nil {
		if err := w.connect(); err != nil {
			connectFailed(err)
			return
		}
	}

	for _, line := range bytes.Split(lines, []byte{'\n'}) {
		// Logentries ignores blank lines
		if len(line) == 0 {
			continue
		}

		_, err := fmt.Fprintf(w.conn, "%s %s\n", w.Token, line)
		if err != nil {
			writeFailed(err)
			if err = w.connect(); err != nil {
				connectFailed(err)
				continue
			}
			fmt.Fprintf(w.conn, "%s %s\n", w.Token, line)
		}
	}
}

func connectFailed(err error) {
	fmt.Fprintf(os.Stderr, "ERROR: Couldn't connect to %s: %s", LOGENTRIES_ADDRESS, err.Error())
}

func writeFailed(err error) {
	fmt.Fprintf(os.Stderr, "ERROR: Couldn't write to %s: %s", LOGENTRIES_ADDRESS, err.Error())
}
<commit_msg>Fix race condition.<commit_after>package le

import (
	"bytes"
	"fmt"
	"net"
	"os"
	"sync"
	"time"
)

const (
	LOGENTRIES_ADDRESS = "data.logentries.com:10000"
)

type Writer struct {
	Token     string
	conn      net.Conn
	channel   chan []byte
	waitGroup sync.WaitGroup
}

// New returns a new Writer with a given Logentries token and buffer size. If
// the token is invalid, Logentries will ignore all submitted logs. The buffer
// size is the maximum number of writes that will be queued for sending before
// the Writer begins rejecting new writes.
func New(token string, buffer int) (w *Writer, err error) {
	w = &Writer{
		Token:   token,
		channel: make(chan []byte, buffer),
	}

	if err = w.connect(); err != nil {
		return nil, err
	}

	w.start()

	return w, nil
}

// Write queues lines of text for sending to Logentries. It returns an error
// only if the write buffer is full.
func (w *Writer) Write(p []byte) (n int, err error) {
	if len(w.channel) >= cap(w.channel) {
		return 0, fmt.Errorf("Buffer is full")
	}

	buf := make([]byte, len(p))
	copy(buf, p)
	w.waitGroup.Add(1)
	w.channel <- buf

	return len(p), nil
}

// Wait will block until all queued writes have been sent to Logentries.
func (w *Writer) Wait() {
	w.waitGroup.Wait()
}

func (w *Writer) start() {
	go func() {
		for {
			w.write(<-w.channel)
			w.waitGroup.Done()
		}
	}()
}

func (w *Writer) connect() (err error) {
	if w.conn != nil {
		w.conn.Close()
		w.conn = nil
	}

	w.conn, err = net.DialTimeout("tcp", LOGENTRIES_ADDRESS, time.Second)
	return err
}

func (w *Writer) write(lines []byte) {
	if w.conn == nil {
		if err := w.connect(); err != nil {
			connectFailed(err)
			return
		}
	}

	for _, line := range bytes.Split(lines, []byte{'\n'}) {
		// Logentries ignores blank lines
		if len(line) == 0 {
			continue
		}

		_, err := fmt.Fprintf(w.conn, "%s %s\n", w.Token, line)
		if err != nil {
			writeFailed(err)
			if err = w.connect(); err != nil {
				connectFailed(err)
				continue
			}
			fmt.Fprintf(w.conn, "%s %s\n", w.Token, line)
		}
	}
}

func connectFailed(err error) {
	fmt.Fprintf(os.Stderr, "ERROR: Couldn't connect to %s: %s", LOGENTRIES_ADDRESS, err.Error())
}

func writeFailed(err error) {
	fmt.Fprintf(os.Stderr, "ERROR: Couldn't write to %s: %s", LOGENTRIES_ADDRESS, err.Error())
}
<|endoftext|>


<commit_before>// Package pages provides a data structure for a web pages.
package pages

import (
	"bytes"
	"encoding/base64"
	"fmt"
	"github.com/ChristianSiegert/go-packages/forms"
	"github.com/ChristianSiegert/go-packages/html"
	"github.com/ChristianSiegert/go-packages/i18n/languages"
	"github.com/ChristianSiegert/go-packages/sessions"
	"html/template"
	"log"
	"net/http"
	"net/url"
)

// Path to root template.
var rootTemplatePath = "./index.html"

// Separator that is used for combining breadcrumbs when Page.Title is called.
var TitleSeparator = " - "

// Templates that are used when Page.ServeEmpty, Error or Page.ServeNotFound is
// called. If a template is nil, only the HTTP status code is set and nothing is
// rendered. To set a different template, set Template[Empty|Error|NotFound]
// from the init function of package main.
var (
	TemplateEmpty    = template.Must(MustNewTemplate("", nil).Parse(`{{define "content"}}{{end}}`))
	TemplateError    *template.Template
	TemplateNotFound = MustNewTemplate("error-pages/404-not-found.html", nil)
)

// SignInUrl is the URL to the page that users are redirected to when
// Page.RequireSignIn is called. If a %s placeholder is present in
// SignInUrl.Path, it is replaced by the page’s language code. E.g.
// “/%s/sign-in” becomes “/en/sign-in” if the page’s language code is “en”.
var SignInUrl = &url.URL{
	Path: "/%s/sign-in",
}

// Page represents a web page.
type Page struct {
	Breadcrumbs []*Breadcrumb

	Data map[string]interface{}

	// Form is an instance of *forms.Form bound to the request.
	Form *forms.Form

	LanguageCode string

	// Name is used to highlight the navigation link of the active page.
	Name string

	Request *http.Request

	ResponseWriter http.ResponseWriter

	Session *sessions.Session

	Template *template.Template

	title string

	TranslateFunc languages.TranslateFunc
}

func NewPage(responseWriter http.ResponseWriter, request *http.Request, languageCode string, translateFunc languages.TranslateFunc, tpl *template.Template) (*Page, error) {
	session, err := sessions.Get(responseWriter, request)
	if err != nil {
		return nil, fmt.Errorf("pages.NewPage: Getting session failed: %s", err)
	}

	page := &Page{
		Form:           forms.New(request),
		LanguageCode:   languageCode,
		Request:        request,
		ResponseWriter: responseWriter,
		Session:        session,
		Template:       tpl,
		TranslateFunc:  translateFunc,
	}

	return page, nil
}

func MustNewPage(responseWriter http.ResponseWriter, request *http.Request, languageCode string, translateFunc languages.TranslateFunc, tpl *template.Template) *Page {
	page, err := NewPage(responseWriter, request, languageCode, translateFunc, tpl)
	if err != nil {
		panic("pages.MustNewPage: " + err.Error())
	}
	return page
}

func (p *Page) AddBreadcrumb(title string, url *url.URL) *Breadcrumb {
	breadcrumb := &Breadcrumb{
		Title: title,
		Url:   url,
	}

	p.Breadcrumbs = append(p.Breadcrumbs, breadcrumb)
	return breadcrumb
}

// RequireSignIn redirects users to the sign-in page specified by SignInUrl.
// If SignInUrl.RawQuery is empty, the query parameters “r” (referrer) and “t”
// (title of the referrer page) are appended. This allows the sign-in page to
// display a message that page <title> is access restricted, and after
// successful authentication, users can be redirected to <referrer>, the page
// they came from.
func (p *Page) RequireSignIn(pageTitle string) {
	u := &url.URL{
		Scheme:   SignInUrl.Scheme,
		Opaque:   SignInUrl.Opaque,
		User:     SignInUrl.User,
		Host:     SignInUrl.Host,
		Path:     fmt.Sprintf(SignInUrl.Path, p.LanguageCode),
		Fragment: SignInUrl.Fragment,
	}

	if SignInUrl.RawQuery == "" {
		query := &url.Values{}
		query.Add("r", p.Request.URL.Path)
		query.Add("t", base64.URLEncoding.EncodeToString([]byte(pageTitle))) // TODO: Sign or encrypt parameter to prevent tempering by users
		u.RawQuery = query.Encode()
	}

	http.Redirect(p.ResponseWriter, p.Request, u.String(), http.StatusSeeOther)
}

// Serve serves the template “index.html” into which it embeds the content
// template specified by page.Template. HTML comments and whitespace are
// stripped. If page.Template is nil, an empty content template is embedded.
func (p *Page) Serve() {
	buffer := bytes.NewBuffer([]byte{})

	if p.Template == nil {
		p.Template = TemplateEmpty
	}

	// If still nil
	if p.Template == nil {
		// context := appengine.NewContext(p.Request)
		// context.Errorf("pages.Serve: Content template is nil. Serving blank page.")
		return
	}

	if err := p.Template.ExecuteTemplate(buffer, "index.html", p); err != nil {
		// context := appengine.NewContext(p.Request)
		// context.Errorf(err.Error())
		Error(p.ResponseWriter, p.Request, p.LanguageCode, p.TranslateFunc, err)
		return
	}

	b := html.RemoveWhitespace(buffer.Bytes())

	if _, err := bytes.NewBuffer(b).WriteTo(p.ResponseWriter); err != nil {
		// context := appengine.NewContext(p.Request)
		// context.Errorf(err.Error())
		Error(p.ResponseWriter, p.Request, p.LanguageCode, p.TranslateFunc, err)
	}
}

// ServeEmpty serves the root template without content template.
func (p *Page) ServeEmpty() {
	p.Template = TemplateEmpty
	p.Serve()
}

// ServeNotFound serves a page that tells the user the requested page does not
// exist.
func (page *Page) ServeNotFound() {
	page.ResponseWriter.WriteHeader(http.StatusNotFound)
	page.Template = TemplateNotFound
	page.Serve()
}

// ServeUnauthorized serves a page that tells the user the requested page cannot
// be accessed due to insufficient access rights.
func (p *Page) ServeUnauthorized() {
	p.Session.AddFlashErrorMessage(p.TranslateFunc("err_unauthorized_access"))
	p.ResponseWriter.WriteHeader(http.StatusUnauthorized)
	p.ServeEmpty()
}

// ServeWithError is similar to Serve, but additionally an error flash message
// is displayed to the user saying that an internal problem occurred. Err is not
// displayed but written to the error log. This method is useful if the user
// should be informed of a problem while the state, e.g. a filled in form, is
// preserved.
func (p *Page) ServeWithError(err error) {
	// context := appengine.NewContext(p.Request)
	// context.Errorf(err.Error())
	p.Session.AddFlashErrorMessage(p.TranslateFunc("err_internal_server_error"))
	p.Serve()
}

// Error is an alias for pages.Error.
func (p *Page) Error(err error) {
	Error(p.ResponseWriter, p.Request, p.LanguageCode, p.TranslateFunc, err)
}

// Title returns the page title if set, or else a title created from bread
// crumbs.
func (p *Page) Title() string {
	if p.title != "" {
		return p.title
	}

	if len(p.Breadcrumbs) > 0 {
		var title string
		for i := len(p.Breadcrumbs) - 1; i >= 0; i-- {
			title += p.Breadcrumbs[i].Title
			if i > 0 {
				title += TitleSeparator
			}
		}
		return title
	}

	return ""
}

// SetTitles sets the page title.
func (p *Page) SetTitle(title string) {
	p.title = title
}

// T returns the translation associated with translationId.
func (p *Page) T(translationId string, templateData ...map[string]interface{}) string {
	return p.TranslateFunc(translationId, templateData...)
}

// Error serves an error page with a generic error message. Err is not displayed
// to the user but written to the error log.
func Error(
	responseWriter http.ResponseWriter,
	request *http.Request,
	languageCode string,
	translateFunc languages.TranslateFunc,
	err error,
) {
	// context := appengine.NewContext(request)
	// context.Errorf(err.Error())
	log.Printf(err.Error())

	if TemplateError == nil {
		// context.Errorf("pages.Error: TemplateError is nil.")
		log.Printf("pages.Error: TemplateError is nil.")
		http.Error(responseWriter, "Internal Server Error", http.StatusInternalServerError)
		return
	}

	buffer := bytes.NewBuffer([]byte{})

	errorPage, err2 := NewPage(responseWriter, request, languageCode, translateFunc, nil)
	if err2 != nil {
		// context.Errorf(err2.Error())
		log.Printf(err2.Error())
		http.Error(responseWriter, "Internal Server Error", http.StatusInternalServerError)
		return
	}

	errorPage.Data = map[string]interface{}{
		"Error":          err,
		"IsDevAppServer": true,
	}

	if err := TemplateError.ExecuteTemplate(buffer, "error.html", errorPage); err != nil {
		// context.Errorf("pages.Error: Executing template failed: %s", err)
		log.Printf("pages.Error: Executing template failed: %s", err)
		http.Error(responseWriter, "Internal Server Error", http.StatusInternalServerError)
		return
	}

	b := html.RemoveWhitespace(buffer.Bytes())

	if _, err := bytes.NewBuffer(b).WriteTo(responseWriter); err != nil {
		// context.Errorf("pages.Error: Writing template to buffer failed: %s", err)
		log.Printf("pages.Error: Writing template to buffer failed: %s", err)
		http.Error(responseWriter, "Internal Server Error", http.StatusInternalServerError)
	}
}

// NewTemplate returns a template consisting of a root template (outer template)
// and a content template (embedded template).
func NewTemplate(rootTemplatePath, contentTemplatePath string, funcMap map[string]interface{}) (*template.Template, error) {
	paths := make([]string, 0, 2)
	paths = append(paths, rootTemplatePath)

	if contentTemplatePath != "" {
		paths = append(paths, contentTemplatePath)
	}

	return template.New("root").Funcs(funcMap).ParseFiles(paths...)
}

// MustNewTemplate is similar to NewTemplate, except that it always uses
// RootTemplatePath to specify the root template. If the root or content
// template cannot be found, the function panics.
func MustNewTemplate(contentTemplatePath string, funcMap map[string]interface{}) *template.Template {
	return template.Must(NewTemplate(rootTemplatePath, contentTemplatePath, funcMap))
}
<commit_msg>Added convenience methods for loading templates.<commit_after>// Package pages provides a data structure for a web pages.
package pages

import (
	"bytes"
	"encoding/base64"
	"fmt"
	"github.com/ChristianSiegert/go-packages/forms"
	"github.com/ChristianSiegert/go-packages/html"
	"github.com/ChristianSiegert/go-packages/i18n/languages"
	"github.com/ChristianSiegert/go-packages/sessions"
	"html/template"
	"log"
	"net/http"
	"net/url"
)

// Path to root template.
var RootTemplatePath = "./templates/index.html"

// Separator that is used for combining breadcrumbs when Page.Title is called.
var TitleSeparator = " - "

// Templates that are used when Page.ServeEmpty, Error or Page.ServeNotFound is
// called. If a template is nil, only the HTTP status code is set and nothing is
// rendered. To set a different template, set Template[Empty|Error|NotFound]
// from the init function of package main.
var (
	TemplateEmpty    = template.Must(MustNewTemplate("", nil).Parse(`{{define "content"}}{{end}}`))
	TemplateError    = MustNewTemplateWithRoot("./templates/error.html", "./templates/500-internal-server-error.html", nil)
	TemplateNotFound = MustNewTemplate("./templates/404-not-found.html", nil)
)

// SignInUrl is the URL to the page that users are redirected to when
// Page.RequireSignIn is called. If a %s placeholder is present in
// SignInUrl.Path, it is replaced by the page’s language code. E.g.
// “/%s/sign-in” becomes “/en/sign-in” if the page’s language code is “en”.
var SignInUrl = &url.URL{
	Path: "/%s/sign-in",
}

// Page represents a web page.
type Page struct {
	Breadcrumbs []*Breadcrumb

	Data map[string]interface{}

	// Form is an instance of *forms.Form bound to the request.
	Form *forms.Form

	LanguageCode string

	// Name is used to highlight the navigation link of the active page.
	Name string

	Request *http.Request

	ResponseWriter http.ResponseWriter

	Session *sessions.Session

	Template *template.Template

	title string

	TranslateFunc languages.TranslateFunc
}

func NewPage(responseWriter http.ResponseWriter, request *http.Request, languageCode string, translateFunc languages.TranslateFunc, tpl *template.Template) (*Page, error) {
	session, err := sessions.Get(responseWriter, request)
	if err != nil {
		return nil, fmt.Errorf("pages.NewPage: Getting session failed: %s", err)
	}

	page := &Page{
		Form:           forms.New(request),
		LanguageCode:   languageCode,
		Request:        request,
		ResponseWriter: responseWriter,
		Session:        session,
		Template:       tpl,
		TranslateFunc:  translateFunc,
	}

	return page, nil
}

func MustNewPage(responseWriter http.ResponseWriter, request *http.Request, languageCode string, translateFunc languages.TranslateFunc, tpl *template.Template) *Page {
	page, err := NewPage(responseWriter, request, languageCode, translateFunc, tpl)
	if err != nil {
		panic("pages.MustNewPage: " + err.Error())
	}
	return page
}

func (p *Page) AddBreadcrumb(title string, url *url.URL) *Breadcrumb {
	breadcrumb := &Breadcrumb{
		Title: title,
		Url:   url,
	}

	p.Breadcrumbs = append(p.Breadcrumbs, breadcrumb)
	return breadcrumb
}

// RequireSignIn redirects users to the sign-in page specified by SignInUrl.
// If SignInUrl.RawQuery is empty, the query parameters “r” (referrer) and “t”
// (title of the referrer page) are appended. This allows the sign-in page to
// display a message that page <title> is access restricted, and after
// successful authentication, users can be redirected to <referrer>, the page
// they came from.
func (p *Page) RequireSignIn(pageTitle string) {
	u := &url.URL{
		Scheme:   SignInUrl.Scheme,
		Opaque:   SignInUrl.Opaque,
		User:     SignInUrl.User,
		Host:     SignInUrl.Host,
		Path:     fmt.Sprintf(SignInUrl.Path, p.LanguageCode),
		Fragment: SignInUrl.Fragment,
	}

	if SignInUrl.RawQuery == "" {
		query := &url.Values{}
		query.Add("r", p.Request.URL.Path)
		query.Add("t", base64.URLEncoding.EncodeToString([]byte(pageTitle))) // TODO: Sign or encrypt parameter to prevent tempering by users
		u.RawQuery = query.Encode()
	}

	http.Redirect(p.ResponseWriter, p.Request, u.String(), http.StatusSeeOther)
}

// Serve serves the template “index.html” into which it embeds the content
// template specified by page.Template. HTML comments and whitespace are
// stripped. If page.Template is nil, an empty content template is embedded.
func (p *Page) Serve() {
	buffer := bytes.NewBuffer([]byte{})

	if p.Template == nil {
		p.Template = TemplateEmpty
	}

	// If still nil
	if p.Template == nil {
		// context := appengine.NewContext(p.Request)
		// context.Errorf("pages.Serve: Content template is nil. Serving blank page.")
		return
	}

	if err := p.Template.ExecuteTemplate(buffer, "index.html", p); err != nil {
		// context := appengine.NewContext(p.Request)
		// context.Errorf(err.Error())
		Error(p.ResponseWriter, p.Request, p.LanguageCode, p.TranslateFunc, err)
		return
	}

	b := html.RemoveWhitespace(buffer.Bytes())

	if _, err := bytes.NewBuffer(b).WriteTo(p.ResponseWriter); err != nil {
		// context := appengine.NewContext(p.Request)
		// context.Errorf(err.Error())
		Error(p.ResponseWriter, p.Request, p.LanguageCode, p.TranslateFunc, err)
	}
}

// ServeEmpty serves the root template without content template.
func (p *Page) ServeEmpty() {
	p.Template = TemplateEmpty
	p.Serve()
}

// ServeNotFound serves a page that tells the user the requested page does not
// exist.
func (page *Page) ServeNotFound() {
	page.ResponseWriter.WriteHeader(http.StatusNotFound)
	page.Template = TemplateNotFound
	page.Serve()
}

// ServeUnauthorized serves a page that tells the user the requested page cannot
// be accessed due to insufficient access rights.
func (p *Page) ServeUnauthorized() {
	p.Session.AddFlashErrorMessage(p.TranslateFunc("err_unauthorized_access"))
	p.ResponseWriter.WriteHeader(http.StatusUnauthorized)
	p.ServeEmpty()
}

// ServeWithError is similar to Serve, but additionally an error flash message
// is displayed to the user saying that an internal problem occurred. Err is not
// displayed but written to the error log. This method is useful if the user
// should be informed of a problem while the state, e.g. a filled in form, is
// preserved.
func (p *Page) ServeWithError(err error) {
	// context := appengine.NewContext(p.Request)
	// context.Errorf(err.Error())
	p.Session.AddFlashErrorMessage(p.TranslateFunc("err_internal_server_error"))
	p.Serve()
}

// Error is an alias for pages.Error.
func (p *Page) Error(err error) {
	Error(p.ResponseWriter, p.Request, p.LanguageCode, p.TranslateFunc, err)
}

// Title returns the page title if set, or else a title created from bread
// crumbs.
func (p *Page) Title() string {
	if p.title != "" {
		return p.title
	}

	if len(p.Breadcrumbs) > 0 {
		var title string
		for i := len(p.Breadcrumbs) - 1; i >= 0; i-- {
			title += p.Breadcrumbs[i].Title
			if i > 0 {
				title += TitleSeparator
			}
		}
		return title
	}

	return ""
}

// SetTitles sets the page title.
func (p *Page) SetTitle(title string) {
	p.title = title
}

// T returns the translation associated with translationId.
func (p *Page) T(translationId string, templateData ...map[string]interface{}) string {
	return p.TranslateFunc(translationId, templateData...)
}

// Error serves an error page with a generic error message. Err is not displayed
// to the user but written to the error log.
func Error(
	responseWriter http.ResponseWriter,
	request *http.Request,
	languageCode string,
	translateFunc languages.TranslateFunc,
	err error,
) {
	// context := appengine.NewContext(request)
	// context.Errorf(err.Error())
	log.Printf(err.Error())

	if TemplateError == nil {
		// context.Errorf("pages.Error: TemplateError is nil.")
		log.Printf("pages.Error: TemplateError is nil.")
		http.Error(responseWriter, "Internal Server Error", http.StatusInternalServerError)
		return
	}

	buffer := bytes.NewBuffer([]byte{})

	errorPage, err2 := NewPage(responseWriter, request, languageCode, translateFunc, nil)
	if err2 != nil {
		// context.Errorf(err2.Error())
		log.Printf(err2.Error())
		http.Error(responseWriter, "Internal Server Error", http.StatusInternalServerError)
		return
	}

	errorPage.Data = map[string]interface{}{
		"Error":          err,
		"IsDevAppServer": true,
	}

	if err := TemplateError.ExecuteTemplate(buffer, "error.html", errorPage); err != nil {
		// context.Errorf("pages.Error: Executing template failed: %s", err)
		log.Printf("pages.Error: Executing template failed: %s", err)
		http.Error(responseWriter, "Internal Server Error", http.StatusInternalServerError)
		return
	}

	b := html.RemoveWhitespace(buffer.Bytes())

	if _, err := bytes.NewBuffer(b).WriteTo(responseWriter); err != nil {
		// context.Errorf("pages.Error: Writing template to buffer failed: %s", err)
		log.Printf("pages.Error: Writing template to buffer failed: %s", err)
		http.Error(responseWriter, "Internal Server Error", http.StatusInternalServerError)
	}
}

// NewTemplateWithRoot loads a root template and embeds the content template in
// it. The content template is embedded at the location of
// {{template "content" .}} in the root template.
func NewTemplateWithRoot(rootTemplatePath, contentTemplatePath string, funcMap template.FuncMap) (*template.Template, error) {
	paths := make([]string, 0, 2)
	paths = append(paths, rootTemplatePath)

	if contentTemplatePath != "" {
		paths = append(paths, contentTemplatePath)
	}

	return template.New("root").Funcs(funcMap).ParseFiles(paths...)
}

// MustNewTemplateWithRoot calls NewTemplateWithRoot. If the root or content
// template cannot be found, the function panics.
func MustNewTemplateWithRoot(rootTemplatePath, contentTemplatePath string, funcMap template.FuncMap) *template.Template {
	return template.Must(NewTemplateWithRoot(rootTemplatePath, contentTemplatePath, funcMap))
}

// NewTemplate loads the default root template specified by RootTemplatePath and
// embeds the content template in it. The content template is embedded at the
// location of {{template "content" .}} in the root template.
func NewTemplate(contentTemplatePath string, funcMap template.FuncMap) (*template.Template, error) {
	return NewTemplateWithRoot(RootTemplatePath, contentTemplatePath, funcMap)
}

// MustNewTemplate calls NewTemplate. If the root or content template cannot be
// found, the function panics.
func MustNewTemplate(contentTemplatePath string, funcMap template.FuncMap) *template.Template {
	return template.Must(NewTemplate(contentTemplatePath, funcMap))
}
<|endoftext|>


<commit_before>package pms

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

	"github.com/ambientsound/pms/api"
	"github.com/ambientsound/pms/console"
	"github.com/ambientsound/pms/db"
	"github.com/ambientsound/pms/index"
	"github.com/ambientsound/pms/input"
	"github.com/ambientsound/pms/input/keys"
	"github.com/ambientsound/pms/message"
	pms_mpd "github.com/ambientsound/pms/mpd"
	"github.com/ambientsound/pms/options"
	"github.com/ambientsound/pms/song"
	"github.com/ambientsound/pms/songlist"
	"github.com/ambientsound/pms/style"
	"github.com/ambientsound/pms/widgets"
	"github.com/gdamore/tcell"

	"github.com/ambientsound/gompd/mpd"
)

// PMS is a kitchen sink of different objects, glued together as a singleton class.
type PMS struct {
	CLI        *input.CLI
	ui         *widgets.UI
	Options    *options.Options
	Sequencer  *keys.Sequencer
	stylesheet style.Stylesheet
	mutex      sync.Mutex

	// collection of data
	database *db.Instance

	// MPD connection object
	Connection *Connection

	// Local versions of MPD's queue and song library, in addition to the song library version that was indexed.
	queueVersion   int
	libraryVersion int
	indexVersion   int

	// EventList receives a signal when current songlist has been changed.
	EventList chan int

	// EventLibrary receives a signal when MPD's library has been updated and retrieved.
	EventLibrary chan int

	// EventMessage is used to display text in the statusbar.
	EventMessage chan message.Message

	// EventOption receives a signal when options have been changed.
	EventOption chan string

	// EventPlayer receives a signal when MPD's "player" status changes in an IDLE event.
	EventPlayer chan int

	// EventPlayer receives a signal when MPD's "playlist" status changes in an IDLE event.
	EventQueue chan int

	// EventPlayer receives a signal when PMS should quit.
	QuitSignal chan int
}

func makeAddress(host, port string) string {
	return fmt.Sprintf("%s:%s", host, port)
}

func (pms *PMS) Message(format string, a ...interface{}) {
	pms.EventMessage <- message.Format(format, a...)
}

func (pms *PMS) Error(format string, a ...interface{}) {
	pms.EventMessage <- message.Errorf(format, a...)
}

func (pms *PMS) Wait() {
	pms.ui.Wait()
}

// handleConnected (re)synchronizes MPD's state with PMS.
func (pms *PMS) handleConnected() {
	var err error

	console.Log("New connection to MPD.")

	console.Log("Updating current song...")
	err = pms.UpdateCurrentSong()
	if err != nil {
		goto errors
	}

	console.Log("Synchronizing queue...")
	err = pms.SyncQueue()
	if err != nil {
		goto errors
	}

	console.Log("Synchronizing library...")
	err = pms.SyncLibrary()
	if err != nil {
		goto errors
	}

	pms.Message("Ready.")

	return

errors:

	pms.Error("ERROR: %s", err)
	pms.Connection.Close()
}

func (pms *PMS) Database() *db.Instance {
	return pms.database
}

// CurrentMpdClient ensures there is a valid MPD connection, and returns the MPD client object.
func (pms *PMS) CurrentMpdClient() *mpd.Client {
	client, err := pms.Connection.MpdClient()
	if err != nil {
		pms.Error("%s", err)
	}
	return client
}

// CurrentSonglistWidget returns the current songlist.
func (pms *PMS) CurrentSonglistWidget() api.SonglistWidget {
	return pms.ui.Songlist
}

// Stylesheet returns the global stylesheet.
func (pms *PMS) Stylesheet() style.Stylesheet {
	return pms.stylesheet
}

// Multibar returns the multibar widget.
func (pms *PMS) Multibar() api.MultibarWidget {
	return pms.ui.Multibar
}

// UI returns the tcell UI widget.
func (pms *PMS) UI() api.UI {
	return pms.ui
}

// RunTicker starts a ticker that will increase the elapsed time every second.
func (pms *PMS) RunTicker() {
	ticker := time.NewTicker(time.Millisecond * 1000)
	defer ticker.Stop()
	for range ticker.C {
		pms.database.SetPlayerStatus(pms.database.PlayerStatus().Tick())
		pms.EventPlayer <- 0
	}
}

// SyncLibrary retrieves the MPD library and stores it as a Songlist in the
// PMS.Library variable. Furthermore, the search index is opened, and if it is
// older than the database version, a reindex task is started.
//
// If the Songlist or Index is cached at the correct version, that part goes untouched.
func (pms *PMS) SyncLibrary() error {
	client, err := pms.Connection.MpdClient()
	if err != nil {
		return err
	}

	stats, err := client.Stats()
	if err != nil {
		return fmt.Errorf("Error while retrieving library stats from MPD: %s", err)
	}

	currentLibrary := pms.database.Library()
	version, _ := strconv.Atoi(stats["db_update"])
	localVersion := currentLibrary.Version()
	console.Log("SyncLibrary(): server reports library version %d", version)
	console.Log("SyncLibrary(): local version is %d", localVersion)

	if version != localVersion {
		console.Log("Switching MPD libraries.")
		console.Log("Closing search index.")
		currentLibrary.CloseIndex()

		console.Log("Retrieving library metadata, %s songs...", stats["songs"])
		library, err := pms.retrieveLibrary()
		if err != nil {
			return fmt.Errorf("Error while retrieving library from MPD: %s", err)
		}

		library.SetVersion(version)
		library.OpenIndex(index.Path(pms.Connection.Host, pms.Connection.Port))

		pms.database.SetLibrary(library)

		console.Log("Library metadata at version %d.", version)

		pms.EventLibrary <- 1
	}

	library := pms.database.Library()
	if !library.IndexSynced() {
		console.Log("Search index is not synchronized with library, rebuilding index...")
		library.ReIndex()
	}

	return nil
}

func (pms *PMS) SyncQueue() error {
	if err := pms.UpdatePlayerStatus(); err != nil {
		return err
	}

	status := pms.database.PlayerStatus()
	if pms.queueVersion == status.Playlist {
		return nil
	}
	queue := pms.database.Queue()

	console.Log("Retrieving changed songs in queue...")
	queueChanges, err := pms.retrieveQueue()
	if err != nil {
		return fmt.Errorf("Error while retrieving queue from MPD: %s", err)
	}
	console.Log("Total of %d changed songs in queue.", queueChanges.Len())
	newQueue, err := queue.Merge(queueChanges)
	if err != nil {
		return fmt.Errorf("Error while merging queue changes: %s", err)
	}
	if err := newQueue.Truncate(status.PlaylistLength); err != nil {
		return fmt.Errorf("Error while truncating queue: %s", err)
	}

	// Replace list while preserving cursor position, either at song ID, or if
	// that failed, place it at the nearest position.
	if err := newQueue.CursorToSong(queue.CursorSong()); err != nil {
		newQueue.SetCursor(queue.Cursor())
	}

	pms.database.SetQueue(newQueue)
	pms.queueVersion = status.Playlist
	console.Log("Queue at version %d.", pms.queueVersion)
	pms.EventQueue <- 1
	return nil
}

func (pms *PMS) retrieveLibrary() (*songlist.Library, error) {
	client, err := pms.Connection.MpdClient()
	if err != nil {
		return nil, err
	}

	timer := time.Now()
	list, err := client.ListAllInfo("/")
	if err != nil {
		return nil, err
	}
	console.Log("ListAllInfo in %s", time.Since(timer).String())

	console.Log("Building library...")

	timer = time.Now()
	s := songlist.NewLibrary()
	s.AddFromAttrlist(list)
	console.Log("Built library in %s", time.Since(timer).String())

	return s, nil
}

func (pms *PMS) retrieveQueue() (*songlist.Queue, error) {
	client, err := pms.Connection.MpdClient()
	if err != nil {
		return nil, err
	}

	timer := time.Now()
	list, err := client.PlChanges(pms.queueVersion, -1, -1)
	if err != nil {
		return nil, err
	}
	console.Log("PlChanges in %s", time.Since(timer).String())

	s := songlist.NewQueue(pms.CurrentMpdClient)
	s.AddFromAttrlist(list)
	return s, nil
}

// UpdateCurrentSong stores a local copy of the currently playing song.
func (pms *PMS) UpdateCurrentSong() error {
	client, err := pms.Connection.MpdClient()
	if err != nil {
		return err
	}

	attrs, err := client.CurrentSong()
	if err != nil {
		return err
	}

	console.Log("MPD current song: %s", attrs["file"])

	s := song.New()
	s.SetTags(attrs)
	pms.database.SetCurrentSong(s)

	pms.EventPlayer <- 0

	return nil
}

// UpdatePlayerStatus populates pms.mpdStatus with data from the MPD server.
func (pms *PMS) UpdatePlayerStatus() error {
	client, err := pms.Connection.MpdClient()
	if err != nil {
		return err
	}

	attrs, err := client.Status()
	if err != nil {
		return err
	}

	status := pms_mpd.PlayerStatus{}
	status.SetTime()

	console.Log("MPD player status: %s", attrs)

	status.Audio = attrs["audio"]
	status.Err = attrs["err"]
	status.State = attrs["state"]

	// The time field is divided into ELAPSED:LENGTH.
	// We only need the length field, since the elapsed field is sent as a
	// floating point value.
	split := strings.Split(attrs["time"], ":")
	if len(split) == 2 {
		status.Time, _ = strconv.Atoi(split[1])
	} else {
		status.Time = -1
	}

	status.Bitrate, _ = strconv.Atoi(attrs["bitrate"])
	status.Playlist, _ = strconv.Atoi(attrs["playlist"])
	status.PlaylistLength, _ = strconv.Atoi(attrs["playlistlength"])
	status.Song, _ = strconv.Atoi(attrs["song"])
	status.SongID, _ = strconv.Atoi(attrs["songid"])
	status.Volume, _ = strconv.Atoi(attrs["volume"])

	status.Elapsed, _ = strconv.ParseFloat(attrs["elapsed"], 64)
	status.ElapsedPercentage, _ = strconv.ParseFloat(attrs["elapsedpercentage"], 64)
	status.MixRampDB, _ = strconv.ParseFloat(attrs["mixrampdb"], 64)

	status.Consume, _ = strconv.ParseBool(attrs["consume"])
	status.Random, _ = strconv.ParseBool(attrs["random"])
	status.Repeat, _ = strconv.ParseBool(attrs["repeat"])
	status.Single, _ = strconv.ParseBool(attrs["single"])

	pms.EventPlayer <- 0

	// Make sure any error messages are relayed to the user
	if len(attrs["error"]) > 0 {
		pms.Error(attrs["error"])
	}

	pms.database.SetPlayerStatus(status)

	return nil
}

// KeyInput receives key input signals, checks the sequencer for key bindings,
// and runs commands if key bindings are found.
func (pms *PMS) KeyInput(ev *tcell.EventKey) {
	matches := pms.Sequencer.KeyInput(ev)
	seqString := pms.Sequencer.String()
	statusText := seqString

	input := pms.Sequencer.Match()
	if !matches || input != nil {
		// Reset statusbar if there is either no match or a complete match.
		statusText = ""
	}

	pms.EventMessage <- message.Sequencef(statusText)

	if input == nil {
		return
	}

	//console.Log("Input sequencer matches bind: '%s' -> '%s'", seqString, input.Command)
	pms.ui.EventInputCommand <- input.Command
}

func (pms *PMS) Execute(cmd string) {
	console.Log("Execute command: '%s'", cmd)
	err := pms.CLI.Execute(cmd)
	if err != nil {
		pms.Error("%s", err)
	}
}
<commit_msg>add error messages for failed index operations; refs #122<commit_after>package pms

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

	"github.com/ambientsound/pms/api"
	"github.com/ambientsound/pms/console"
	"github.com/ambientsound/pms/db"
	"github.com/ambientsound/pms/index"
	"github.com/ambientsound/pms/input"
	"github.com/ambientsound/pms/input/keys"
	"github.com/ambientsound/pms/message"
	pms_mpd "github.com/ambientsound/pms/mpd"
	"github.com/ambientsound/pms/options"
	"github.com/ambientsound/pms/song"
	"github.com/ambientsound/pms/songlist"
	"github.com/ambientsound/pms/style"
	"github.com/ambientsound/pms/widgets"
	"github.com/gdamore/tcell"

	"github.com/ambientsound/gompd/mpd"
)

// PMS is a kitchen sink of different objects, glued together as a singleton class.
type PMS struct {
	CLI        *input.CLI
	ui         *widgets.UI
	Options    *options.Options
	Sequencer  *keys.Sequencer
	stylesheet style.Stylesheet
	mutex      sync.Mutex

	// collection of data
	database *db.Instance

	// MPD connection object
	Connection *Connection

	// Local versions of MPD's queue and song library, in addition to the song library version that was indexed.
	queueVersion   int
	libraryVersion int
	indexVersion   int

	// EventList receives a signal when current songlist has been changed.
	EventList chan int

	// EventLibrary receives a signal when MPD's library has been updated and retrieved.
	EventLibrary chan int

	// EventMessage is used to display text in the statusbar.
	EventMessage chan message.Message

	// EventOption receives a signal when options have been changed.
	EventOption chan string

	// EventPlayer receives a signal when MPD's "player" status changes in an IDLE event.
	EventPlayer chan int

	// EventPlayer receives a signal when MPD's "playlist" status changes in an IDLE event.
	EventQueue chan int

	// EventPlayer receives a signal when PMS should quit.
	QuitSignal chan int
}

func makeAddress(host, port string) string {
	return fmt.Sprintf("%s:%s", host, port)
}

func (pms *PMS) Message(format string, a ...interface{}) {
	pms.EventMessage <- message.Format(format, a...)
}

func (pms *PMS) Error(format string, a ...interface{}) {
	pms.EventMessage <- message.Errorf(format, a...)
}

func (pms *PMS) Wait() {
	pms.ui.Wait()
}

// handleConnected (re)synchronizes MPD's state with PMS.
func (pms *PMS) handleConnected() {
	var err error

	console.Log("New connection to MPD.")

	console.Log("Updating current song...")
	err = pms.UpdateCurrentSong()
	if err != nil {
		goto errors
	}

	console.Log("Synchronizing queue...")
	err = pms.SyncQueue()
	if err != nil {
		goto errors
	}

	console.Log("Synchronizing library...")
	err = pms.SyncLibrary()
	if err != nil {
		goto errors
	}

	pms.Message("Ready.")

	return

errors:

	pms.Error("ERROR: %s", err)
	pms.Connection.Close()
}

func (pms *PMS) Database() *db.Instance {
	return pms.database
}

// CurrentMpdClient ensures there is a valid MPD connection, and returns the MPD client object.
func (pms *PMS) CurrentMpdClient() *mpd.Client {
	client, err := pms.Connection.MpdClient()
	if err != nil {
		pms.Error("%s", err)
	}
	return client
}

// CurrentSonglistWidget returns the current songlist.
func (pms *PMS) CurrentSonglistWidget() api.SonglistWidget {
	return pms.ui.Songlist
}

// Stylesheet returns the global stylesheet.
func (pms *PMS) Stylesheet() style.Stylesheet {
	return pms.stylesheet
}

// Multibar returns the multibar widget.
func (pms *PMS) Multibar() api.MultibarWidget {
	return pms.ui.Multibar
}

// UI returns the tcell UI widget.
func (pms *PMS) UI() api.UI {
	return pms.ui
}

// RunTicker starts a ticker that will increase the elapsed time every second.
func (pms *PMS) RunTicker() {
	ticker := time.NewTicker(time.Millisecond * 1000)
	defer ticker.Stop()
	for range ticker.C {
		pms.database.SetPlayerStatus(pms.database.PlayerStatus().Tick())
		pms.EventPlayer <- 0
	}
}

// SyncLibrary retrieves the MPD library and stores it as a Songlist in the
// PMS.Library variable. Furthermore, the search index is opened, and if it is
// older than the database version, a reindex task is started.
//
// If the Songlist or Index is cached at the correct version, that part goes untouched.
func (pms *PMS) SyncLibrary() error {
	client, err := pms.Connection.MpdClient()
	if err != nil {
		return err
	}

	stats, err := client.Stats()
	if err != nil {
		return fmt.Errorf("Error while retrieving library stats from MPD: %s", err)
	}

	currentLibrary := pms.database.Library()
	version, _ := strconv.Atoi(stats["db_update"])
	localVersion := currentLibrary.Version()
	console.Log("SyncLibrary(): server reports library version %d", version)
	console.Log("SyncLibrary(): local version is %d", localVersion)

	if version != localVersion {
		console.Log("Switching MPD libraries.")
		console.Log("Closing search index.")
		err = currentLibrary.CloseIndex()
		if err != nil {
			console.Log("Error closing search index: %s", err)
		}

		console.Log("Retrieving library metadata, %s songs...", stats["songs"])
		library, err := pms.retrieveLibrary()
		if err != nil {
			return fmt.Errorf("Error while retrieving library from MPD: %s", err)
		}

		library.SetVersion(version)
		err = library.OpenIndex(index.Path(pms.Connection.Host, pms.Connection.Port))
		if err != nil {
			console.Log("Error opening search index: %s", err)
		}

		pms.database.SetLibrary(library)

		console.Log("Library metadata at version %d.", version)

		pms.EventLibrary <- 1
	}

	library := pms.database.Library()
	if !library.IndexSynced() {
		if !library.HasIndex() {
			console.Log("Want to synchronize index with library, but no index available!")
		} else {
			console.Log("Search index is not synchronized with library, rebuilding index...")
			library.ReIndex()
		}
	}

	return nil
}

func (pms *PMS) SyncQueue() error {
	if err := pms.UpdatePlayerStatus(); err != nil {
		return err
	}

	status := pms.database.PlayerStatus()
	if pms.queueVersion == status.Playlist {
		return nil
	}
	queue := pms.database.Queue()

	console.Log("Retrieving changed songs in queue...")
	queueChanges, err := pms.retrieveQueue()
	if err != nil {
		return fmt.Errorf("Error while retrieving queue from MPD: %s", err)
	}
	console.Log("Total of %d changed songs in queue.", queueChanges.Len())
	newQueue, err := queue.Merge(queueChanges)
	if err != nil {
		return fmt.Errorf("Error while merging queue changes: %s", err)
	}
	if err := newQueue.Truncate(status.PlaylistLength); err != nil {
		return fmt.Errorf("Error while truncating queue: %s", err)
	}

	// Replace list while preserving cursor position, either at song ID, or if
	// that failed, place it at the nearest position.
	if err := newQueue.CursorToSong(queue.CursorSong()); err != nil {
		newQueue.SetCursor(queue.Cursor())
	}

	pms.database.SetQueue(newQueue)
	pms.queueVersion = status.Playlist
	console.Log("Queue at version %d.", pms.queueVersion)
	pms.EventQueue <- 1
	return nil
}

func (pms *PMS) retrieveLibrary() (*songlist.Library, error) {
	client, err := pms.Connection.MpdClient()
	if err != nil {
		return nil, err
	}

	timer := time.Now()
	list, err := client.ListAllInfo("/")
	if err != nil {
		return nil, err
	}
	console.Log("ListAllInfo in %s", time.Since(timer).String())

	console.Log("Building library...")

	timer = time.Now()
	s := songlist.NewLibrary()
	s.AddFromAttrlist(list)
	console.Log("Built library in %s", time.Since(timer).String())

	return s, nil
}

func (pms *PMS) retrieveQueue() (*songlist.Queue, error) {
	client, err := pms.Connection.MpdClient()
	if err != nil {
		return nil, err
	}

	timer := time.Now()
	list, err := client.PlChanges(pms.queueVersion, -1, -1)
	if err != nil {
		return nil, err
	}
	console.Log("PlChanges in %s", time.Since(timer).String())

	s := songlist.NewQueue(pms.CurrentMpdClient)
	s.AddFromAttrlist(list)
	return s, nil
}

// UpdateCurrentSong stores a local copy of the currently playing song.
func (pms *PMS) UpdateCurrentSong() error {
	client, err := pms.Connection.MpdClient()
	if err != nil {
		return err
	}

	attrs, err := client.CurrentSong()
	if err != nil {
		return err
	}

	console.Log("MPD current song: %s", attrs["file"])

	s := song.New()
	s.SetTags(attrs)
	pms.database.SetCurrentSong(s)

	pms.EventPlayer <- 0

	return nil
}

// UpdatePlayerStatus populates pms.mpdStatus with data from the MPD server.
func (pms *PMS) UpdatePlayerStatus() error {
	client, err := pms.Connection.MpdClient()
	if err != nil {
		return err
	}

	attrs, err := client.Status()
	if err != nil {
		return err
	}

	status := pms_mpd.PlayerStatus{}
	status.SetTime()

	console.Log("MPD player status: %s", attrs)

	status.Audio = attrs["audio"]
	status.Err = attrs["err"]
	status.State = attrs["state"]

	// The time field is divided into ELAPSED:LENGTH.
	// We only need the length field, since the elapsed field is sent as a
	// floating point value.
	split := strings.Split(attrs["time"], ":")
	if len(split) == 2 {
		status.Time, _ = strconv.Atoi(split[1])
	} else {
		status.Time = -1
	}

	status.Bitrate, _ = strconv.Atoi(attrs["bitrate"])
	status.Playlist, _ = strconv.Atoi(attrs["playlist"])
	status.PlaylistLength, _ = strconv.Atoi(attrs["playlistlength"])
	status.Song, _ = strconv.Atoi(attrs["song"])
	status.SongID, _ = strconv.Atoi(attrs["songid"])
	status.Volume, _ = strconv.Atoi(attrs["volume"])

	status.Elapsed, _ = strconv.ParseFloat(attrs["elapsed"], 64)
	status.ElapsedPercentage, _ = strconv.ParseFloat(attrs["elapsedpercentage"], 64)
	status.MixRampDB, _ = strconv.ParseFloat(attrs["mixrampdb"], 64)

	status.Consume, _ = strconv.ParseBool(attrs["consume"])
	status.Random, _ = strconv.ParseBool(attrs["random"])
	status.Repeat, _ = strconv.ParseBool(attrs["repeat"])
	status.Single, _ = strconv.ParseBool(attrs["single"])

	pms.EventPlayer <- 0

	// Make sure any error messages are relayed to the user
	if len(attrs["error"]) > 0 {
		pms.Error(attrs["error"])
	}

	pms.database.SetPlayerStatus(status)

	return nil
}

// KeyInput receives key input signals, checks the sequencer for key bindings,
// and runs commands if key bindings are found.
func (pms *PMS) KeyInput(ev *tcell.EventKey) {
	matches := pms.Sequencer.KeyInput(ev)
	seqString := pms.Sequencer.String()
	statusText := seqString

	input := pms.Sequencer.Match()
	if !matches || input != nil {
		// Reset statusbar if there is either no match or a complete match.
		statusText = ""
	}

	pms.EventMessage <- message.Sequencef(statusText)

	if input == nil {
		return
	}

	// console.Log("Input sequencer matches bind: '%s' -> '%s'", seqString, input.Command)
	pms.ui.EventInputCommand <- input.Command
}

func (pms *PMS) Execute(cmd string) {
	console.Log("Execute command: '%s'", cmd)
	err := pms.CLI.Execute(cmd)
	if err != nil {
		pms.Error("%s", err)
	}
}
<|endoftext|>


<commit_before>// Copyright 2011 Rob Thornton. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build linux bsd unix

/* ncurses panel extension

The following functions have not been implemented because there are far more
effective manners by which they can be done in Go: set_panel_userptr() and
panel_userptr() */
package goncurses

// #cgo LDFLAGS: -lpanel
// #include <panel.h>
// #include <ncurses.h>
import "C"

import "errors"

type Panel struct {
	pan *C.PANEL
}

// Panel creates a new panel derived from the window, adding it to the
// panel stack. The pointer to the original window can still be used to
// excute most window functions with the exception of Refresh(). Always
// use panel's Refresh() function.
func NewPanel(w *Window) *Panel {
	return &Panel{C.new_panel(w.win)}
}

// UpdatePanels refreshes the panel stack. It must be called prior to
// using ncurses's DoUpdate()
func UpdatePanels() {
	C.update_panels()
	return
}

// Returns a pointer to the panel above in the stack or nil. Passing nil will
// return the top panel in the stack
func (p *Panel) Above() *Panel {
	return &Panel{C.panel_above(p.pan)}
}

// Returns a pointer to the panel below in the stack or nil. Passing nil will
// return the bottom panel in the stack
func Below(p *Panel) *Panel {
	return &Panel{C.panel_above(p.pan)}
}

// Move the panel to the bottom of the stack.
func (p *Panel) Bottom() error {
	if C.bottom_panel(p.pan) == C.ERR {
		return errors.New("Failed to move panel to bottom of stack")
	}
	return nil
}

// Delete panel, removing from the stack.
func (p *Panel) Delete() error {
	if C.del_panel(p.pan) == C.ERR {
		return errors.New("Failed to delete panel")
	}
	p = nil
	return nil
}

// Hidden returns true if panel is visible, false if not
func (p *Panel) Hidden() bool {
	return C.panel_hidden(p.pan) == C.TRUE
}

// Hide the panel
func (p *Panel) Hide() error {
	if C.hide_panel(p.pan) == C.ERR {
		return errors.New("Failed to hide panel")
	}
	return nil
}

// Move the panel to the specified location. It is important to never use
// ncurses movement functions on the window governed by panel. Always use
// this function
func (p *Panel) Move(y, x int) error {
	if C.move_panel(p.pan, C.int(y), C.int(x)) == C.ERR {
		return errors.New("Failed to move panel")
	}
	return nil
}

// Replace panel's associated window with a new one.
func (p *Panel) Replace(w *Window) error {
	if C.replace_panel(p.pan, w.win) == C.ERR {
		return errors.New("Failed to replace window")
	}
	return nil
}

// Show the panel, if hidden, and place it on the top of the stack.
func (p *Panel) Show() error {
	if C.show_panel(p.pan) == C.ERR {
		return errors.New("Failed to show panel")
	}
	return nil
}

// Move panel to the top of the stack
func (p *Panel) Top() error {
	if C.top_panel(p.pan) == C.ERR {
		return errors.New("Failed to move panel to top of stack")
	}
	return nil
}

// Window returns the window governed by panel
func (p *Panel) Window() *Window {
	return &Window{C.panel_window(p.pan)}
}
<commit_msg>Update godoc comment for panel extension<commit_after>// Copyright 2011 Rob Thornton. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build linux bsd unix

/*
The following functions have not been implemented because there are far more
effective manners by which they can be done in Go: set_panel_userptr() and
panel_userptr() */
package goncurses

// #cgo LDFLAGS: -lpanel
// #include <panel.h>
// #include <ncurses.h>
import "C"

import "errors"

type Panel struct {
	pan *C.PANEL
}

// Panel creates a new panel derived from the window, adding it to the
// panel stack. The pointer to the original window can still be used to
// excute most window functions with the exception of Refresh(). Always
// use panel's Refresh() function.
func NewPanel(w *Window) *Panel {
	return &Panel{C.new_panel(w.win)}
}

// UpdatePanels refreshes the panel stack. It must be called prior to
// using ncurses's DoUpdate()
func UpdatePanels() {
	C.update_panels()
	return
}

// Returns a pointer to the panel above in the stack or nil. Passing nil will
// return the top panel in the stack
func (p *Panel) Above() *Panel {
	return &Panel{C.panel_above(p.pan)}
}

// Returns a pointer to the panel below in the stack or nil. Passing nil will
// return the bottom panel in the stack
func Below(p *Panel) *Panel {
	return &Panel{C.panel_above(p.pan)}
}

// Move the panel to the bottom of the stack.
func (p *Panel) Bottom() error {
	if C.bottom_panel(p.pan) == C.ERR {
		return errors.New("Failed to move panel to bottom of stack")
	}
	return nil
}

// Delete panel, removing from the stack.
func (p *Panel) Delete() error {
	if C.del_panel(p.pan) == C.ERR {
		return errors.New("Failed to delete panel")
	}
	p = nil
	return nil
}

// Hidden returns true if panel is visible, false if not
func (p *Panel) Hidden() bool {
	return C.panel_hidden(p.pan) == C.TRUE
}

// Hide the panel
func (p *Panel) Hide() error {
	if C.hide_panel(p.pan) == C.ERR {
		return errors.New("Failed to hide panel")
	}
	return nil
}

// Move the panel to the specified location. It is important to never use
// ncurses movement functions on the window governed by panel. Always use
// this function
func (p *Panel) Move(y, x int) error {
	if C.move_panel(p.pan, C.int(y), C.int(x)) == C.ERR {
		return errors.New("Failed to move panel")
	}
	return nil
}

// Replace panel's associated window with a new one.
func (p *Panel) Replace(w *Window) error {
	if C.replace_panel(p.pan, w.win) == C.ERR {
		return errors.New("Failed to replace window")
	}
	return nil
}

// Show the panel, if hidden, and place it on the top of the stack.
func (p *Panel) Show() error {
	if C.show_panel(p.pan) == C.ERR {
		return errors.New("Failed to show panel")
	}
	return nil
}

// Move panel to the top of the stack
func (p *Panel) Top() error {
	if C.top_panel(p.pan) == C.ERR {
		return errors.New("Failed to move panel to top of stack")
	}
	return nil
}

// Window returns the window governed by panel
func (p *Panel) Window() *Window {
	return &Window{C.panel_window(p.pan)}
}
<|endoftext|>