<commit_before>// Copyright 2015 YP LLC.
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
package main

// unit tests that don't rely on ceph

import (
	"flag"
	"fmt"
	"os"
	"testing"

	dkvolume "github.com/docker/go-plugins-helpers/volume"
	"github.com/stretchr/testify/assert"
)

// TODO: tests that need ceph
// make fake cluster?
// use dockerized container with ceph for tests?

const (
	TEST_SOCKET_PATH             = "/tmp/rbd-test.sock"
	EXPECTED_ACTIVATION_RESPONSE = "{\"Implements\": [\"VolumeDriver\"]}"
)

var (
	testDriver cephRBDVolumeDriver
)

func TestMain(m *testing.M) {
	flag.Parse()
	cephConf := os.Getenv("CEPH_CONF")

	testDriver = newCephRBDVolumeDriver(
		"test",
		"",
		"admin",
		"rbd",
		dkvolume.DefaultDockerRootDirectory,
		cephConf,
		false,
	)
	defer testDriver.shutdown()

	handler := dkvolume.NewHandler(testDriver)
	// Serve won't return so spin off routine
	go handler.ServeUnix("", TEST_SOCKET_PATH)

	os.Exit(m.Run())
}

func TestLocalLockerCookie(t *testing.T) {
	assert.NotEqual(t, "HOST_UNKNOWN", testDriver.localLockerCookie())
}

func TestRbdImageExists_noName(t *testing.T) {
	f_bool, err := testDriver.rbdImageExists(testDriver.pool, "")
	assert.Equal(t, false, f_bool, fmt.Sprintf("%s", err))
}

func TestRbdImageExists_withName(t *testing.T) {
	t.Skip("This fails for many reasons. Need to figure out how to do this in a container.")
	err := testDriver.createRBDImage("rbd", "foo", 1, "xfs")
	assert.Nil(t, err, formatError("createRBDImage", err))
	t_bool, err := testDriver.rbdImageExists(testDriver.pool, "foo")
	assert.Equal(t, true, t_bool, formatError("rbdImageExists", err))
}

// cephRBDDriver.parseImagePoolNameSize(string) (string, string, int, error)
func TestParseImagePoolNameSize_name(t *testing.T) {
	pool, name, size := parseImageAndHandleError(t, "foo")

	assert.Equal(t, testDriver.pool, pool, "Pool should be same")
	assert.Equal(t, "foo", name, "Name should be same")
	assert.Equal(t, *defaultImageSizeMB, size, "Size should be same")
}

func TestParseImagePoolNameSize_complexName(t *testing.T) {
	pool, name, size := parseImageAndHandleError(t, "es-data1_v2.3")

	assert.Equal(t, testDriver.pool, pool, "Pool should be same")
	assert.Equal(t, "es-data1_v2.3", name, "Name should be same")
	assert.Equal(t, *defaultImageSizeMB, size, "Size should be same")
}

func TestParseImagePoolNameSize_withPool(t *testing.T) {
	pool, name, size := parseImageAndHandleError(t, "liverpool/foo")

	assert.Equal(t, "liverpool", pool, "Pool should be same")
	assert.Equal(t, "foo", name, "Name should be same")
	assert.Equal(t, *defaultImageSizeMB, size, "Size should be same")
}

func TestParseImagePoolNameSize_withSize(t *testing.T) {
	pool, name, size := parseImageAndHandleError(t, "liverpool/foo@1024")

	assert.Equal(t, "liverpool", pool, "Pool should be same")
	assert.Equal(t, "foo", name, "Name should be same")
	assert.Equal(t, 1024, size, "Size should be same")
}

func TestParseImagePoolNameSize_withPoolAndSize(t *testing.T) {
	pool, name, size := parseImageAndHandleError(t, "foo@1024")

	assert.Equal(t, testDriver.pool, pool, "Pool should be same")
	assert.Equal(t, "foo", name, "Name should be same")
	assert.Equal(t, 1024, size, "Size should be same")
}

// need a way to test the socket access using basic format - since this broke
// in golang 1.6 with strict Host header checking even if using Unix sockets.
// Requires socat and sudo
//
// Error response when built with golang 1.6: 400 Bad Request: missing required Host header
func TestSocketActivate(t *testing.T) {
	out, err := sh("bash", "-c", "echo \"POST /Plugin.Activate HTTP/1.1\r\n\" | sudo socat unix-connect:/tmp/rbd-test.sock STDIO")
	assert.Nil(t, err, formatError("socat plugin activate", err))
	assert.Contains(t, out, EXPECTED_ACTIVATION_RESPONSE, "Expecting Implements VolumeDriver message")

}

func TestSh_success(t *testing.T) {
	out, err := sh("ls")
	assert.Nil(t, err, formatError("ls", err))
	assert.Contains(t, out, "driver_test.go")
}

func TestSh_fail(t *testing.T) {
	_, err := sh("false")
	assert.NotNil(t, err, formatError("false", err))
}

// Helpers
func formatError(name string, err error) string {
	return fmt.Sprintf("ERROR calling %s: %q", name, err)
}

func parseImageAndHandleError(t *testing.T, name string) (string, string, int) {
	pool, name, size, err := testDriver.parseImagePoolNameSize(name)
	assert.Nil(t, err, formatError("parseImagePoolNameSize", err))
	return pool, name, size
}
<commit_msg>Skip a failing test for now, requires sudo for initial socket creation still<commit_after>// Copyright 2015 YP LLC.
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
package main

// unit tests that don't rely on ceph

import (
	"flag"
	"fmt"
	"os"
	"testing"

	dkvolume "github.com/docker/go-plugins-helpers/volume"
	"github.com/stretchr/testify/assert"
)

// TODO: tests that need ceph
// make fake cluster?
// use dockerized container with ceph for tests?

const (
	TEST_SOCKET_PATH             = "/tmp/rbd-test.sock"
	EXPECTED_ACTIVATION_RESPONSE = "{\"Implements\": [\"VolumeDriver\"]}"
)

var (
	testDriver cephRBDVolumeDriver
)

func TestMain(m *testing.M) {
	flag.Parse()
	cephConf := os.Getenv("CEPH_CONF")

	testDriver = newCephRBDVolumeDriver(
		"test",
		"",
		"admin",
		"rbd",
		dkvolume.DefaultDockerRootDirectory,
		cephConf,
		false,
	)
	defer testDriver.shutdown()

	handler := dkvolume.NewHandler(testDriver)
	// Serve won't return so spin off routine
	go handler.ServeUnix("", TEST_SOCKET_PATH)

	os.Exit(m.Run())
}

func TestLocalLockerCookie(t *testing.T) {
	assert.NotEqual(t, "HOST_UNKNOWN", testDriver.localLockerCookie())
}

func TestRbdImageExists_noName(t *testing.T) {
	f_bool, err := testDriver.rbdImageExists(testDriver.pool, "")
	assert.Equal(t, false, f_bool, fmt.Sprintf("%s", err))
}

func TestRbdImageExists_withName(t *testing.T) {
	t.Skip("This fails for many reasons. Need to figure out how to do this in a container.")
	err := testDriver.createRBDImage("rbd", "foo", 1, "xfs")
	assert.Nil(t, err, formatError("createRBDImage", err))
	t_bool, err := testDriver.rbdImageExists(testDriver.pool, "foo")
	assert.Equal(t, true, t_bool, formatError("rbdImageExists", err))
}

// cephRBDDriver.parseImagePoolNameSize(string) (string, string, int, error)
func TestParseImagePoolNameSize_name(t *testing.T) {
	pool, name, size := parseImageAndHandleError(t, "foo")

	assert.Equal(t, testDriver.pool, pool, "Pool should be same")
	assert.Equal(t, "foo", name, "Name should be same")
	assert.Equal(t, *defaultImageSizeMB, size, "Size should be same")
}

func TestParseImagePoolNameSize_complexName(t *testing.T) {
	pool, name, size := parseImageAndHandleError(t, "es-data1_v2.3")

	assert.Equal(t, testDriver.pool, pool, "Pool should be same")
	assert.Equal(t, "es-data1_v2.3", name, "Name should be same")
	assert.Equal(t, *defaultImageSizeMB, size, "Size should be same")
}

func TestParseImagePoolNameSize_withPool(t *testing.T) {
	pool, name, size := parseImageAndHandleError(t, "liverpool/foo")

	assert.Equal(t, "liverpool", pool, "Pool should be same")
	assert.Equal(t, "foo", name, "Name should be same")
	assert.Equal(t, *defaultImageSizeMB, size, "Size should be same")
}

func TestParseImagePoolNameSize_withSize(t *testing.T) {
	pool, name, size := parseImageAndHandleError(t, "liverpool/foo@1024")

	assert.Equal(t, "liverpool", pool, "Pool should be same")
	assert.Equal(t, "foo", name, "Name should be same")
	assert.Equal(t, 1024, size, "Size should be same")
}

func TestParseImagePoolNameSize_withPoolAndSize(t *testing.T) {
	pool, name, size := parseImageAndHandleError(t, "foo@1024")

	assert.Equal(t, testDriver.pool, pool, "Pool should be same")
	assert.Equal(t, "foo", name, "Name should be same")
	assert.Equal(t, 1024, size, "Size should be same")
}

// need a way to test the socket access using basic format - since this broke
// in golang 1.6 with strict Host header checking even if using Unix sockets.
// Requires socat and sudo
//
// Error response when built with golang 1.6: 400 Bad Request: missing required Host header
func TestSocketActivate(t *testing.T) {
	t.Skip("This test requires socket, which seems to need root privs to build. So this test fails if run as normal user. TODO: Find a proper workaround.")
	out, err := sh("bash", "-c", "echo \"POST /Plugin.Activate HTTP/1.1\r\n\" | sudo socat unix-connect:/tmp/rbd-test.sock STDIO")
	assert.Nil(t, err, formatError("socat plugin activate", err))
	assert.Contains(t, out, EXPECTED_ACTIVATION_RESPONSE, "Expecting Implements VolumeDriver message")

}

func TestSh_success(t *testing.T) {
	out, err := sh("ls")
	assert.Nil(t, err, formatError("ls", err))
	assert.Contains(t, out, "driver_test.go")
}

func TestSh_fail(t *testing.T) {
	_, err := sh("false")
	assert.NotNil(t, err, formatError("false", err))
}

// Helpers
func formatError(name string, err error) string {
	return fmt.Sprintf("ERROR calling %s: %q", name, err)
}

func parseImageAndHandleError(t *testing.T, name string) (string, string, int) {
	pool, name, size, err := testDriver.parseImagePoolNameSize(name)
	assert.Nil(t, err, formatError("parseImagePoolNameSize", err))
	return pool, name, size
}
<|endoftext|>


<commit_before>package drone

// User represents a user account.
type User struct {
	ID     int64  `json:"id"`
	Login  string `json:"login"`
	Email  string `json:"email"`
	Avatar string `json:"avatar_url"`
	Active bool   `json:"active"`
	Admin  bool   `json:"admin"`
}

// Repo represents a version control repository.
type Repo struct {
	ID          int64  `json:"id"`
	Owner       string `json:"owner"`
	Name        string `json:"name"`
	FullName    string `json:"full_name"`
	Avatar      string `json:"avatar_url"`
	Link        string `json:"link_url"`
	Clone       string `json:"clone_url"`
	Branch      string `json:"default_branch"`
	Timeout     int64  `json:"timeout"`
	IsPrivate   bool   `json:"private"`
	IsTrusted   bool   `json:"trusted"`
	AllowPull   bool   `json:"allow_pr"`
	AllowPush   bool   `json:"allow_push"`
	AllowDeploy bool   `json:"allow_deploys"`
	AllowTag    bool   `json:"allow_tags"`
}

// Build represents the process of compiling and testing a changeset,
// typically triggered by the remote system (ie GitHub).
type Build struct {
	ID        int64  `json:"id"`
	Number    int    `json:"number"`
	Event     string `json:"event"`
	Status    string `json:"status"`
	Enqueued  int64  `json:"enqueued_at"`
	Created   int64  `json:"created_at"`
	Started   int64  `json:"started_at"`
	Finished  int64  `json:"finished_at"`
	Deploy    string `json:"deploy_to"`
	Commit    string `json:"commit"`
	Branch    string `json:"branch"`
	Ref       string `json:"ref"`
	Refspec   string `json:"refspec"`
	Remote    string `json:"remote"`
	Title     string `json:"title"`
	Message   string `json:"message"`
	Timestamp int64  `json:"timestamp"`
	Author    string `json:"author"`
	Avatar    string `json:"author_avatar"`
	Email     string `json:"author_email"`
	Link      string `json:"link_url"`
}

// Job represents a single job that is being executed as part
// of a Build.
type Job struct {
	ID       int64  `json:"id"`
	Number   int    `json:"number"`
	Status   string `json:"status"`
	ExitCode int    `json:"exit_code"`
	Enqueued int64  `json:"enqueued_at"`
	Started  int64  `json:"started_at"`
	Finished int64  `json:"finished_at"`

	Environment map[string]string `json:"environment"`
}

// Secret represents a repository secret.
type Secret struct {
	ID    int64  `json:"id"`
	Name  string `json:"name"`
	Value string `json:"value"`
	Image string `json:"image"`
	Event string `json:"event"`
}

// Activity represents a build activity. It combines the
// build details with summary Repository information.
type Activity struct {
	Owner     string `json:"owner"`
	Name      string `json:"name"`
	FullName  string `json:"full_name"`
	Number    int    `json:"number"`
	Event     string `json:"event"`
	Status    string `json:"status"`
	Enqueued  int64  `json:"enqueued_at"`
	Created   int64  `json:"created_at"`
	Started   int64  `json:"started_at"`
	Finished  int64  `json:"finished_at"`
	Commit    string `json:"commit"`
	Branch    string `json:"branch"`
	Ref       string `json:"ref"`
	Refspec   string `json:"refspec"`
	Remote    string `json:"remote"`
	Title     string `json:"title"`
	Message   string `json:"message"`
	Timestamp int64  `json:"timestamp"`
	Author    string `json:"author"`
	Avatar    string `json:"author_avatar"`
	Email     string `json:"author_email"`
	Link      string `json:"link_url"`
}

// Node represents a local or remote Docker daemon that is
// responsible for running jobs.
type Node struct {
	ID   int64  `json:"id"`
	Addr string `json:"address"`
	Arch string `json:"architecture"`
	Cert string `json:"cert"`
	Key  string `json:"key"`
	CA   string `json:"ca"`
}

// Key represents an RSA public and private key assigned to a
// repository. It may be used to clone private repositories, or as
// a deployment key.
type Key struct {
	Public  string `json:"public"`
	Private string `json:"private"`
}

// Netrc defines a default .netrc file that should be injected
// into the build environment. It will be used to authorize access
// to https resources, such as git+https clones.
type Netrc struct {
	Machine  string `json:"machine"`
	Login    string `json:"login"`
	Password string `json:"user"`
}

// System represents the drone system.
type System struct {
	Version   string   `json:"version"`
	Link      string   `json:"link_url"`
	Plugins   []string `json:"plugins"`
	Globals   []string `json:"globals"`
	Escalates []string `json:"privileged_plugins"`
}

// Workspace defines the build's workspace inside the
// container. This helps the plugin locate the source
// code directory.
type Workspace struct {
	Root string `json:"root"`
	Path string `json:"path"`

	Netrc *Netrc `json:"netrc"`
	Keys  *Key   `json:"keys"`
}

// Payload defines the full payload send to plugins.
type Payload struct {
	Yaml      string      `json:"config"`
	YamlEnc   string      `json:"secret"`
	Repo      *Repo       `json:"repo"`
	Build     *Build      `json:"build"`
	BuildLast *Build      `json:"build_last"`
	Job       *Job        `json:"job"`
	Netrc     *Netrc      `json:"netrc"`
	Keys      *Key        `json:"keys"`
	System    *System     `json:"system"`
	Workspace *Workspace  `json:"workspace"`
	Vargs     interface{} `json:"vargs"`
}
<commit_msg>use string slice for secret filters<commit_after>package drone

// User represents a user account.
type User struct {
	ID     int64  `json:"id"`
	Login  string `json:"login"`
	Email  string `json:"email"`
	Avatar string `json:"avatar_url"`
	Active bool   `json:"active"`
	Admin  bool   `json:"admin"`
}

// Repo represents a version control repository.
type Repo struct {
	ID          int64  `json:"id"`
	Owner       string `json:"owner"`
	Name        string `json:"name"`
	FullName    string `json:"full_name"`
	Avatar      string `json:"avatar_url"`
	Link        string `json:"link_url"`
	Clone       string `json:"clone_url"`
	Branch      string `json:"default_branch"`
	Timeout     int64  `json:"timeout"`
	IsPrivate   bool   `json:"private"`
	IsTrusted   bool   `json:"trusted"`
	AllowPull   bool   `json:"allow_pr"`
	AllowPush   bool   `json:"allow_push"`
	AllowDeploy bool   `json:"allow_deploys"`
	AllowTag    bool   `json:"allow_tags"`
}

// Build represents the process of compiling and testing a changeset,
// typically triggered by the remote system (ie GitHub).
type Build struct {
	ID        int64  `json:"id"`
	Number    int    `json:"number"`
	Event     string `json:"event"`
	Status    string `json:"status"`
	Enqueued  int64  `json:"enqueued_at"`
	Created   int64  `json:"created_at"`
	Started   int64  `json:"started_at"`
	Finished  int64  `json:"finished_at"`
	Deploy    string `json:"deploy_to"`
	Commit    string `json:"commit"`
	Branch    string `json:"branch"`
	Ref       string `json:"ref"`
	Refspec   string `json:"refspec"`
	Remote    string `json:"remote"`
	Title     string `json:"title"`
	Message   string `json:"message"`
	Timestamp int64  `json:"timestamp"`
	Author    string `json:"author"`
	Avatar    string `json:"author_avatar"`
	Email     string `json:"author_email"`
	Link      string `json:"link_url"`
}

// Job represents a single job that is being executed as part
// of a Build.
type Job struct {
	ID       int64  `json:"id"`
	Number   int    `json:"number"`
	Status   string `json:"status"`
	ExitCode int    `json:"exit_code"`
	Enqueued int64  `json:"enqueued_at"`
	Started  int64  `json:"started_at"`
	Finished int64  `json:"finished_at"`

	Environment map[string]string `json:"environment"`
}

// Secret represents a repository secret.
type Secret struct {
	ID    int64    `json:"id"`
	Name  string   `json:"name"`
	Value string   `json:"value"`
	Image []string `json:"image"`
	Event []string `json:"event"`
}

// Activity represents a build activity. It combines the
// build details with summary Repository information.
type Activity struct {
	Owner     string `json:"owner"`
	Name      string `json:"name"`
	FullName  string `json:"full_name"`
	Number    int    `json:"number"`
	Event     string `json:"event"`
	Status    string `json:"status"`
	Enqueued  int64  `json:"enqueued_at"`
	Created   int64  `json:"created_at"`
	Started   int64  `json:"started_at"`
	Finished  int64  `json:"finished_at"`
	Commit    string `json:"commit"`
	Branch    string `json:"branch"`
	Ref       string `json:"ref"`
	Refspec   string `json:"refspec"`
	Remote    string `json:"remote"`
	Title     string `json:"title"`
	Message   string `json:"message"`
	Timestamp int64  `json:"timestamp"`
	Author    string `json:"author"`
	Avatar    string `json:"author_avatar"`
	Email     string `json:"author_email"`
	Link      string `json:"link_url"`
}

// Node represents a local or remote Docker daemon that is
// responsible for running jobs.
type Node struct {
	ID   int64  `json:"id"`
	Addr string `json:"address"`
	Arch string `json:"architecture"`
	Cert string `json:"cert"`
	Key  string `json:"key"`
	CA   string `json:"ca"`
}

// Key represents an RSA public and private key assigned to a
// repository. It may be used to clone private repositories, or as
// a deployment key.
type Key struct {
	Public  string `json:"public"`
	Private string `json:"private"`
}

// Netrc defines a default .netrc file that should be injected
// into the build environment. It will be used to authorize access
// to https resources, such as git+https clones.
type Netrc struct {
	Machine  string `json:"machine"`
	Login    string `json:"login"`
	Password string `json:"user"`
}

// System represents the drone system.
type System struct {
	Version   string   `json:"version"`
	Link      string   `json:"link_url"`
	Plugins   []string `json:"plugins"`
	Globals   []string `json:"globals"`
	Escalates []string `json:"privileged_plugins"`
}

// Workspace defines the build's workspace inside the
// container. This helps the plugin locate the source
// code directory.
type Workspace struct {
	Root string `json:"root"`
	Path string `json:"path"`

	Netrc *Netrc `json:"netrc"`
	Keys  *Key   `json:"keys"`
}

// Payload defines the full payload send to plugins.
type Payload struct {
	Yaml      string      `json:"config"`
	YamlEnc   string      `json:"secret"`
	Repo      *Repo       `json:"repo"`
	Build     *Build      `json:"build"`
	BuildLast *Build      `json:"build_last"`
	Job       *Job        `json:"job"`
	Netrc     *Netrc      `json:"netrc"`
	Keys      *Key        `json:"keys"`
	System    *System     `json:"system"`
	Workspace *Workspace  `json:"workspace"`
	Vargs     interface{} `json:"vargs"`
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/dynport/gocli"
	"os"
	"strconv"
	"time"
)

func init() {
	cli.Register("droplet/info",
		&gocli.Action{
			Handler:     DescribeDropletAction,
			Description: "Describe Droplet",
		},
	)
}

func DescribeDropletAction(args *gocli.Args) error {
	if len(args.Args) != 1 {
		return fmt.Errorf("USAGE: <droplet_id>")
	}
	i, e := strconv.Atoi(args.Args[0])
	if e != nil {
		return e
	}
	droplet, e := CurrentAccount().GetDroplet(i)
	if e != nil {
		return e
	}
	table := gocli.NewTable()
	table.Add("Id", fmt.Sprintf("%d", droplet.Id))
	table.Add("Name", droplet.Name)
	table.Add("Status", droplet.Status)
	table.Add("Locked", strconv.FormatBool(droplet.Locked))
	fmt.Println(table)
	return nil
}

func init() {
	cli.Register(
		"droplet/list",
		&gocli.Action{
			Handler:     ListDropletsAction,
			Description: "List active droplets",
		},
	)
}

func ListDropletsAction(args *gocli.Args) (e error) {
	logger.Debug("listing droplets")

	droplets, e := CurrentAccount().Droplets()
	if e != nil {
		return e
	}

	if _, e := CurrentAccount().CachedSizes(); e != nil {
		return e
	}

	table := gocli.NewTable()
	if len(droplets) == 0 {
		table.Add("no droplets found")
	} else {
		table.Add("Id", "Created", "Status", "Locked", "Name", "IPAddress", "Region", "Size", "Image")
		for _, droplet := range droplets {
			table.Add(
				strconv.Itoa(droplet.Id),
				droplet.CreatedAt.Format("2006-01-02T15:04"),
				droplet.Status,
				strconv.FormatBool(droplet.Locked),
				droplet.Name,
				droplet.IpAddress,
				fmt.Sprintf("%s (%d)", CurrentAccount().RegionName(droplet.RegionId), droplet.RegionId),
				fmt.Sprintf("%s (%d)", CurrentAccount().SizeName(droplet.SizeId), droplet.SizeId),
				fmt.Sprintf("%s (%d)", CurrentAccount().ImageName(droplet.ImageId), droplet.ImageId),
			)
		}
	}
	fmt.Fprintln(os.Stdout, table.String())
	return nil
}

func init() {
	args := &gocli.Args{}
	args.RegisterInt("-i", false, CurrentAccount().ImageId, "Image id for new droplet")
	args.RegisterInt("-r", false, CurrentAccount().RegionId, "Region id for new droplet")
	args.RegisterInt("-s", false, CurrentAccount().SizeId, "Size id for new droplet")
	args.RegisterInt("-k", false, CurrentAccount().SshKey, "Ssh key to be used")

	cli.Register(
		"droplet/create",
		&gocli.Action{
			Description: "Create new droplet",
			Usage:       "<name>",
			Handler:     CreateDropletAction,
			Args:        args,
		},
	)
}

func CreateDropletAction(a *gocli.Args) error {
	logger.Debugf("would create a new droplet with %#v", a.Args)
	if len(a.Args) != 1 {
		return fmt.Errorf("USAGE: create droplet <name>")
	}
	droplet := &Droplet{Name: a.Args[0]}

	var e error
	if droplet.SizeId, e = a.GetInt("-s"); e != nil {
		return e
	}

	if droplet.ImageId, e = a.GetInt("-i"); e != nil {
		return e
	}

	if droplet.RegionId, e = a.GetInt("-r"); e != nil {
		return e
	}

	if droplet.SshKey, e = a.GetInt("-k"); e != nil {
		return e
	}

	droplet, e = CurrentAccount().CreateDroplet(droplet)
	if e != nil {
		return e
	}
	droplet.Account = CurrentAccount()
	logger.Infof("created droplet with id %d", droplet.Id)
	e = WaitForDroplet(droplet)
	logger.Infof("droplet %d ready", droplet.Id)
	return e
}

func init() {
	cli.Register(
		"droplet/destroy",
		&gocli.Action{
			Description: "Destroy droplet",
			Handler:     DestroyDropletAction,
			Usage:       "<droplet_id>",
		},
	)
}

func DestroyDropletAction(args *gocli.Args) error {
	logger.Debugf("would destroy droplet with %#v", args)
	if len(args.Args) == 0 {
		return fmt.Errorf("USAGE: droplet destroy id1,id2,id3")
	}
	for _, id := range args.Args {
		if i, e := strconv.Atoi(id); e == nil {
			logger.Prefix = fmt.Sprintf("droplet-%d", i)
			droplet, e := CurrentAccount().GetDroplet(i)
			if e != nil {
				logger.Errorf("unable to get droplet for %d", i)
				continue
			}
			logger.Infof("destroying droplet %d", droplet.Id)
			rsp, e := CurrentAccount().DestroyDroplet(droplet.Id)
			if e != nil {
				return e
			}
			logger.Debugf("got response %+v", rsp)
			started := time.Now()
			archived := false
			for i := 0; i < 300; i++ {
				droplet.Reload()
				if droplet.Status == "archive" || droplet.Status == "off" {
					archived = true
					break
				}
				logger.Debug("status " + droplet.Status)
				fmt.Print(".")
				time.Sleep(1 * time.Second)
			}
			logger.Info("\ndroplet destroyed")
			if !archived {
				logger.Errorf("error archiving %d", droplet.Id)
			} else {
				logger.Debugf("archived in %.06f", time.Now().Sub(started).Seconds())
			}
		}
	}
	return nil
}

func init() {
	args := &gocli.Args{}
	args.RegisterInt("-i", false, 0, "Rebuild droplet")
	cli.Register(
		"droplet/rebuild",
		&gocli.Action{
			Description: "Rebuild droplet",
			Handler:     RebuildDropletAction,
			Usage:       "<droplet_id>",
			Args:        args,
		},
	)
}

func RebuildDropletAction(a *gocli.Args) error {
	if len(a.Args) != 1 {
		return fmt.Errorf("USAGE: droplet rebuild <id>")
	}
	i, e := strconv.Atoi(a.Args[0])
	if e != nil {
		return fmt.Errorf("USAGE: droplet rebuild <id>")
	}

	imageId, e := a.GetInt("-i")
	if e != nil {
		return e
	}

	rsp, e := account.RebuildDroplet(i, imageId)
	if e != nil {
		return e
	}
	logger.Debugf("got response %+v", rsp)
	droplet := &Droplet{Id: i, Account: account}
	return WaitForDroplet(droplet)
}
<commit_msg>print total time and ip address when finished launching a droplet<commit_after>package main

import (
	"fmt"
	"github.com/dynport/gocli"
	"os"
	"strconv"
	"time"
)

func init() {
	cli.Register("droplet/info",
		&gocli.Action{
			Handler:     DescribeDropletAction,
			Description: "Describe Droplet",
		},
	)
}

func DescribeDropletAction(args *gocli.Args) error {
	if len(args.Args) != 1 {
		return fmt.Errorf("USAGE: <droplet_id>")
	}
	i, e := strconv.Atoi(args.Args[0])
	if e != nil {
		return e
	}
	droplet, e := CurrentAccount().GetDroplet(i)
	if e != nil {
		return e
	}
	table := gocli.NewTable()
	table.Add("Id", fmt.Sprintf("%d", droplet.Id))
	table.Add("Name", droplet.Name)
	table.Add("Status", droplet.Status)
	table.Add("Locked", strconv.FormatBool(droplet.Locked))
	fmt.Println(table)
	return nil
}

func init() {
	cli.Register(
		"droplet/list",
		&gocli.Action{
			Handler:     ListDropletsAction,
			Description: "List active droplets",
		},
	)
}

func ListDropletsAction(args *gocli.Args) (e error) {
	logger.Debug("listing droplets")

	droplets, e := CurrentAccount().Droplets()
	if e != nil {
		return e
	}

	if _, e := CurrentAccount().CachedSizes(); e != nil {
		return e
	}

	table := gocli.NewTable()
	if len(droplets) == 0 {
		table.Add("no droplets found")
	} else {
		table.Add("Id", "Created", "Status", "Locked", "Name", "IPAddress", "Region", "Size", "Image")
		for _, droplet := range droplets {
			table.Add(
				strconv.Itoa(droplet.Id),
				droplet.CreatedAt.Format("2006-01-02T15:04"),
				droplet.Status,
				strconv.FormatBool(droplet.Locked),
				droplet.Name,
				droplet.IpAddress,
				fmt.Sprintf("%s (%d)", CurrentAccount().RegionName(droplet.RegionId), droplet.RegionId),
				fmt.Sprintf("%s (%d)", CurrentAccount().SizeName(droplet.SizeId), droplet.SizeId),
				fmt.Sprintf("%s (%d)", CurrentAccount().ImageName(droplet.ImageId), droplet.ImageId),
			)
		}
	}
	fmt.Fprintln(os.Stdout, table.String())
	return nil
}

func init() {
	args := &gocli.Args{}
	args.RegisterInt("-i", false, CurrentAccount().ImageId, "Image id for new droplet")
	args.RegisterInt("-r", false, CurrentAccount().RegionId, "Region id for new droplet")
	args.RegisterInt("-s", false, CurrentAccount().SizeId, "Size id for new droplet")
	args.RegisterInt("-k", false, CurrentAccount().SshKey, "Ssh key to be used")

	cli.Register(
		"droplet/create",
		&gocli.Action{
			Description: "Create new droplet",
			Usage:       "<name>",
			Handler:     CreateDropletAction,
			Args:        args,
		},
	)
}

func CreateDropletAction(a *gocli.Args) error {
	started := time.Now()
	logger.Debugf("would create a new droplet with %#v", a.Args)
	if len(a.Args) != 1 {
		return fmt.Errorf("USAGE: create droplet <name>")
	}
	droplet := &Droplet{Name: a.Args[0]}

	var e error
	if droplet.SizeId, e = a.GetInt("-s"); e != nil {
		return e
	}

	if droplet.ImageId, e = a.GetInt("-i"); e != nil {
		return e
	}

	if droplet.RegionId, e = a.GetInt("-r"); e != nil {
		return e
	}

	if droplet.SshKey, e = a.GetInt("-k"); e != nil {
		return e
	}

	droplet, e = CurrentAccount().CreateDroplet(droplet)
	if e != nil {
		return e
	}
	droplet.Account = CurrentAccount()
	logger.Infof("created droplet with id %d", droplet.Id)
	e = WaitForDroplet(droplet)
	logger.Infof("droplet %d ready, ip: %s. total_time: %.1fs", droplet.Id, droplet.IpAddress, time.Now().Sub(started).Seconds())
	return e
}

func init() {
	cli.Register(
		"droplet/destroy",
		&gocli.Action{
			Description: "Destroy droplet",
			Handler:     DestroyDropletAction,
			Usage:       "<droplet_id>",
		},
	)
}

func DestroyDropletAction(args *gocli.Args) error {
	logger.Debugf("would destroy droplet with %#v", args)
	if len(args.Args) == 0 {
		return fmt.Errorf("USAGE: droplet destroy id1,id2,id3")
	}
	for _, id := range args.Args {
		if i, e := strconv.Atoi(id); e == nil {
			logger.Prefix = fmt.Sprintf("droplet-%d", i)
			droplet, e := CurrentAccount().GetDroplet(i)
			if e != nil {
				logger.Errorf("unable to get droplet for %d", i)
				continue
			}
			logger.Infof("destroying droplet %d", droplet.Id)
			rsp, e := CurrentAccount().DestroyDroplet(droplet.Id)
			if e != nil {
				return e
			}
			logger.Debugf("got response %+v", rsp)
			started := time.Now()
			archived := false
			for i := 0; i < 300; i++ {
				droplet.Reload()
				if droplet.Status == "archive" || droplet.Status == "off" {
					archived = true
					break
				}
				logger.Debug("status " + droplet.Status)
				fmt.Print(".")
				time.Sleep(1 * time.Second)
			}
			logger.Info("\ndroplet destroyed")
			if !archived {
				logger.Errorf("error archiving %d", droplet.Id)
			} else {
				logger.Debugf("archived in %.06f", time.Now().Sub(started).Seconds())
			}
		}
	}
	return nil
}

func init() {
	args := &gocli.Args{}
	args.RegisterInt("-i", false, 0, "Rebuild droplet")
	cli.Register(
		"droplet/rebuild",
		&gocli.Action{
			Description: "Rebuild droplet",
			Handler:     RebuildDropletAction,
			Usage:       "<droplet_id>",
			Args:        args,
		},
	)
}

func RebuildDropletAction(a *gocli.Args) error {
	if len(a.Args) != 1 {
		return fmt.Errorf("USAGE: droplet rebuild <id>")
	}
	i, e := strconv.Atoi(a.Args[0])
	if e != nil {
		return fmt.Errorf("USAGE: droplet rebuild <id>")
	}

	imageId, e := a.GetInt("-i")
	if e != nil {
		return e
	}

	rsp, e := account.RebuildDroplet(i, imageId)
	if e != nil {
		return e
	}
	logger.Debugf("got response %+v", rsp)
	droplet := &Droplet{Id: i, Account: account}
	return WaitForDroplet(droplet)
}
<|endoftext|>


<commit_before>// $G $D/$F.go && $L $F.$A && ./$A.out

// Copyright 2009 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.

// Verify channel operations that test for blocking
// Use several sizes and types of operands

package main

func pause() {
	for i:=0; i<100; i++ { sys.gosched() }
}

func i32receiver(c *chan int32) {
	<-c
}

func i32sender(c *chan int32) {
	c -< 1
}

func i64receiver(c *chan int64) {
	<-c
}

func i64sender(c *chan int64) {
	c -< 1
}

func breceiver(c *chan bool) {
	<-c
}

func bsender(c *chan bool) {
	c -< true
}

func sreceiver(c *chan string) {
	<-c
}

func ssender(c *chan string) {
	c -< "hi"
}

func main() {
	var i32 int32;
	var i64 int64;
	var b bool;
	var s string;
	var ok bool;

	c32 := new(chan int32);
	c64 := new(chan int64);
	cb := new(chan bool);
	cs := new(chan string);

	i32, ok = <-c32;
	if ok { panic "blocked i32sender" }

	i64, ok = <-c64;
	if ok { panic "blocked i64sender" }

	b, ok = <-cb;
	if ok { panic "blocked bsender" }

	s, ok = <-cs;
	if ok { panic "blocked ssender" }

	go i32receiver(c32);
	pause();
	ok = c32 -< 1;
	if !ok { panic "i32receiver" }
	go i32sender(c32);
	pause();
	i32, ok = <-c32;
	if !ok { panic "i32sender" }

	go i64receiver(c64);
	pause();
	ok = c64 -< 1;
	if !ok { panic "i64receiver" }
	go i64sender(c64);
	pause();
	i64, ok = <-c64;
	if !ok { panic "i64sender" }

	go breceiver(cb);
	pause();
	ok = cb -< true;
	if !ok { panic "breceiver" }
	go bsender(cb);
	pause();
	b, ok = <-cb;
	if !ok { panic "bsender" }

	go sreceiver(cs);
	pause();
	ok = cs -< "hi";
	if !ok { panic "sreceiver" }
	go ssender(cs);
	pause();
	s, ok = <-cs;
	if !ok { panic "ssender" }
}
<commit_msg>strengthen test by checking values<commit_after>// $G $D/$F.go && $L $F.$A && ./$A.out

// Copyright 2009 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.

// Verify channel operations that test for blocking
// Use several sizes and types of operands

package main

func pause() {
	for i:=0; i<100; i++ { sys.gosched() }
}

func i32receiver(c *chan int32) {
	if <-c != 123 { panic "i32 value" }
}

func i32sender(c *chan int32) {
	c -< 234
}

func i64receiver(c *chan int64) {
	if <-c != 123456 { panic "i64 value" }
}

func i64sender(c *chan int64) {
	c -< 234567
}

func breceiver(c *chan bool) {
	if ! <-c { panic "b value" }
}

func bsender(c *chan bool) {
	c -< true
}

func sreceiver(c *chan string) {
	if <-c != "hello" { panic "s value" }
}

func ssender(c *chan string) {
	c -< "hello again"
}

func main() {
	var i32 int32;
	var i64 int64;
	var b bool;
	var s string;
	var ok bool;

	c32 := new(chan int32);
	c64 := new(chan int64);
	cb := new(chan bool);
	cs := new(chan string);

	i32, ok = <-c32;
	if ok { panic "blocked i32sender" }

	i64, ok = <-c64;
	if ok { panic "blocked i64sender" }

	b, ok = <-cb;
	if ok { panic "blocked bsender" }

	s, ok = <-cs;
	if ok { panic "blocked ssender" }

	go i32receiver(c32);
	pause();
	ok = c32 -< 123;
	if !ok { panic "i32receiver" }
	go i32sender(c32);
	pause();
	i32, ok = <-c32;
	if !ok { panic "i32sender" }
	if i32 != 234 { panic "i32sender value" }

	go i64receiver(c64);
	pause();
	ok = c64 -< 123456;
	if !ok { panic "i64receiver" }
	go i64sender(c64);
	pause();
	i64, ok = <-c64;
	if !ok { panic "i64sender" }
	if i64 != 234567 { panic "i64sender value" }

	go breceiver(cb);
	pause();
	ok = cb -< true;
	if !ok { panic "breceiver" }
	go bsender(cb);
	pause();
	b, ok = <-cb;
	if !ok { panic "bsender" }
	if !b{ panic "bsender value" }

	go sreceiver(cs);
	pause();
	ok = cs -< "hello";
	if !ok { panic "sreceiver" }
	go ssender(cs);
	pause();
	s, ok = <-cs;
	if !ok { panic "ssender" }
	if s != "hello again" { panic "ssender value" }
	print "PASS\n"
}
<|endoftext|>


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

// Package xdgbasedir implements a freedesktop.org XDG Base Directory Specification.
// XDG Base Directory Specification:
//  https://specifications.freedesktop.org/basedir-spec/0.8/
//
// The XDG Base Directory Specification is based on the following concepts:
//
// There is a single base directory relative to which user-specific data files should be written. This directory is defined by the environment variable $XDG_DATA_HOME.
//
// There is a single base directory relative to which user-specific configuration files should be written. This directory is defined by the environment variable $XDG_CONFIG_HOME.
//
// There is a set of preference ordered base directories relative to which data files should be searched. This set of directories is defined by the environment variable $XDG_DATA_DIRS.
//
// There is a set of preference ordered base directories relative to which configuration files should be searched. This set of directories is defined by the environment variable $XDG_CONFIG_DIRS.
//
// There is a single base directory relative to which user-specific non-essential (cached) data should be written. This directory is defined by the environment variable $XDG_CACHE_HOME.
package xdgbasedir

import (
	"log"
	"os"
	"os/user"
	"path/filepath"
	"runtime"
)

var usrHome = os.Getenv("HOME")
var usr = &user.User{}

// TODO(zchee): Support cross-platform compile.
// user.Current() uses cgo build in the Go stdlib internal.
func init() {
	cUser, err := user.Current()
	if err != nil {
		log.Fatal(err)
	}
	usr = cUser
}

// DataHome return the XDG_DATA_HOME based directory path.
//
// $XDG_DATA_HOME defines the base directory relative to which user specific data files should be stored.
// If $XDG_DATA_HOME is either not set or empty, a default equal to $HOME/.local/share should be used.
func DataHome() string {
	if dataHome := os.Getenv("XDG_DATA_HOME"); dataHome != "" {
		return dataHome
	}
	return filepath.Join(homeDir(), ".local", "share")
}

// ConfigHome return the XDG_CONFIG_HOME based directory path.
//
// $XDG_CONFIG_HOME defines the base directory relative to which user specific configuration files should be stored.
// If $XDG_CONFIG_HOME is either not set or empty, a default equal to $HOME/.config should be used.
func ConfigHome() string {
	if configHome := os.Getenv("XDG_CONFIG_HOME"); configHome != "" {
		return configHome
	}
	return filepath.Join(homeDir(), ".config")
}

// DataDirs return the XDG_DATA_DIRS based directory path.
//
// $XDG_DATA_DIRS defines the preference-ordered set of base directories to search for data files in addition
// to the $XDG_DATA_HOME base directory. The directories in $XDG_DATA_DIRS should be seperated with a colon ':'.
// If $XDG_DATA_DIRS is either not set or empty, a value equal to /usr/local/share/:/usr/share/ should be used.
// TODO(zchee): XDG_DATA_DIRS should be seperated with a colon, We should change return type to the []string
// which colon seperated value instead of string?
func DataDirs() string {
	if dataDirs := os.Getenv("XDG_DATA_DIRS"); dataDirs != "" {
		return dataDirs
	}
	return filepath.Join("usr", "local", "share", string(filepath.ListSeparator), "usr", "share")
}

// ConfigDirs return the XDG_CONFIG_DIRS based directory path.
//
// $XDG_CONFIG_DIRS defines the preference-ordered set of base directories to search for configuration files in addition
// to the $XDG_CONFIG_HOME base directory. The directories in $XDG_CONFIG_DIRS should be seperated with a colon ':'.
// If $XDG_CONFIG_DIRS is either not set or empty, a value equal to /etc/xdg should be used.
// TODO(zchee): XDG_CONFIG_DIRS should be seperated with a colon, We should change return type to the []string
// which colon seperated value instead of string?
func ConfigDirs() string {
	if configDirs := os.Getenv("XDG_CONFIG_DIRS"); configDirs != "" {
		return configDirs
	}
	return filepath.Join("etc", "xdg")
}

// CacheHome return the XDG_CACHE_HOME based directory path.
//
// $XDG_CACHE_HOME defines the base directory relative to which user specific non-essential data files should be stored.
// If $XDG_CACHE_HOME is either not set or empty, a default equal to $HOME/.cache should be used.
//
// TODO(zchee): In macOS, Is it better to use the ~/Library/Caches directory? Or add the configurable by users setting?
// Apple's "File System Programming Guide" describe the this directory should be used if users cache files.
// However, some user who is using the macOS as Unix-like prefers $HOME/.cache.
//  https://developer.apple.com/library/content/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/MacOSXDirectories/MacOSXDirectories.html#//apple_ref/doc/uid/TP40010672-CH10-SW1
func CacheHome() string {
	if cacheHome := os.Getenv("XDG_CACHE_HOME"); cacheHome != "" {
		return cacheHome
	}
	return filepath.Join(homeDir(), ".cache")
}

// RuntimeDir return the XDG_RUNTIME_DIR based directory path.
//
// $XDG_RUNTIME_DIR defines the base directory relative to which user-specific non-essential runtime files and
// other file objects (such as sockets, named pipes, ...) should be stored. The directory MUST be owned by the user,
// and he MUST be the only one having read and write access to it. Its Unix access mode MUST be 0700.
//
// TODO(zchee): Avoid use usr.Uid for support the cross-platform compile.
// TODO(zchee): XDG_RUNTIME_DIR seems to change depending on the each distro or init system such as systemd.
// Also In macOS, normal user haven't permission for write to this directory.
func RuntimeDir() string {
	if runtimeDir := os.Getenv("XDG_RUNTIME_DIR"); runtimeDir != "" {
		return runtimeDir
	}
	return filepath.Join("run", "user", usr.Uid)
}

func homeDir() string {
	if usrHome != "" {
		return usrHome
	}

	// TODO(zchee): In Windows OS, which of $HOME and these checks has priority?
	if runtime.GOOS == "windows" {
		usrHome = filepath.Join(os.Getenv("HOMEDRIVE"), os.Getenv("HOMEPATH"))
		if usrHome == "" {
			usrHome = os.Getenv("USERPROFILE")
		}
		return usrHome
	}

	usrHome = usr.HomeDir

	return usrHome
}
<commit_msg>xdgbasedir: add root filepath separator<commit_after>// Copyright 2017 The go-xdgbasedir Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package xdgbasedir implements a freedesktop.org XDG Base Directory Specification.
// XDG Base Directory Specification:
//  https://specifications.freedesktop.org/basedir-spec/0.8/
//
// The XDG Base Directory Specification is based on the following concepts:
//
// There is a single base directory relative to which user-specific data files should be written. This directory is defined by the environment variable $XDG_DATA_HOME.
//
// There is a single base directory relative to which user-specific configuration files should be written. This directory is defined by the environment variable $XDG_CONFIG_HOME.
//
// There is a set of preference ordered base directories relative to which data files should be searched. This set of directories is defined by the environment variable $XDG_DATA_DIRS.
//
// There is a set of preference ordered base directories relative to which configuration files should be searched. This set of directories is defined by the environment variable $XDG_CONFIG_DIRS.
//
// There is a single base directory relative to which user-specific non-essential (cached) data should be written. This directory is defined by the environment variable $XDG_CACHE_HOME.
package xdgbasedir

import (
	"log"
	"os"
	"os/user"
	"path/filepath"
	"runtime"
)

var usrHome = os.Getenv("HOME")
var usr = &user.User{}

// TODO(zchee): Support cross-platform compile.
// user.Current() uses cgo build in the Go stdlib internal.
func init() {
	cUser, err := user.Current()
	if err != nil {
		log.Fatal(err)
	}
	usr = cUser
}

// DataHome return the XDG_DATA_HOME based directory path.
//
// $XDG_DATA_HOME defines the base directory relative to which user specific data files should be stored.
// If $XDG_DATA_HOME is either not set or empty, a default equal to $HOME/.local/share should be used.
func DataHome() string {
	if dataHome := os.Getenv("XDG_DATA_HOME"); dataHome != "" {
		return dataHome
	}
	return filepath.Join(homeDir(), ".local", "share")
}

// ConfigHome return the XDG_CONFIG_HOME based directory path.
//
// $XDG_CONFIG_HOME defines the base directory relative to which user specific configuration files should be stored.
// If $XDG_CONFIG_HOME is either not set or empty, a default equal to $HOME/.config should be used.
func ConfigHome() string {
	if configHome := os.Getenv("XDG_CONFIG_HOME"); configHome != "" {
		return configHome
	}
	return filepath.Join(homeDir(), ".config")
}

// DataDirs return the XDG_DATA_DIRS based directory path.
//
// $XDG_DATA_DIRS defines the preference-ordered set of base directories to search for data files in addition
// to the $XDG_DATA_HOME base directory. The directories in $XDG_DATA_DIRS should be seperated with a colon ':'.
// If $XDG_DATA_DIRS is either not set or empty, a value equal to /usr/local/share/:/usr/share/ should be used.
// TODO(zchee): XDG_DATA_DIRS should be seperated with a colon, We should change return type to the []string
// which colon seperated value instead of string?
func DataDirs() string {
	if dataDirs := os.Getenv("XDG_DATA_DIRS"); dataDirs != "" {
		return dataDirs
	}
	return filepath.Join(string(filepath.Separator), "usr", "local", "share", string(filepath.ListSeparator), "usr", "share")
}

// ConfigDirs return the XDG_CONFIG_DIRS based directory path.
//
// $XDG_CONFIG_DIRS defines the preference-ordered set of base directories to search for configuration files in addition
// to the $XDG_CONFIG_HOME base directory. The directories in $XDG_CONFIG_DIRS should be seperated with a colon ':'.
// If $XDG_CONFIG_DIRS is either not set or empty, a value equal to /etc/xdg should be used.
// TODO(zchee): XDG_CONFIG_DIRS should be seperated with a colon, We should change return type to the []string
// which colon seperated value instead of string?
func ConfigDirs() string {
	if configDirs := os.Getenv("XDG_CONFIG_DIRS"); configDirs != "" {
		return configDirs
	}
	return filepath.Join(string(filepath.Separator), "etc", "xdg")
}

// CacheHome return the XDG_CACHE_HOME based directory path.
//
// $XDG_CACHE_HOME defines the base directory relative to which user specific non-essential data files should be stored.
// If $XDG_CACHE_HOME is either not set or empty, a default equal to $HOME/.cache should be used.
//
// TODO(zchee): In macOS, Is it better to use the ~/Library/Caches directory? Or add the configurable by users setting?
// Apple's "File System Programming Guide" describe the this directory should be used if users cache files.
// However, some user who is using the macOS as Unix-like prefers $HOME/.cache.
//  https://developer.apple.com/library/content/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/MacOSXDirectories/MacOSXDirectories.html#//apple_ref/doc/uid/TP40010672-CH10-SW1
func CacheHome() string {
	if cacheHome := os.Getenv("XDG_CACHE_HOME"); cacheHome != "" {
		return cacheHome
	}
	return filepath.Join(homeDir(), ".cache")
}

// RuntimeDir return the XDG_RUNTIME_DIR based directory path.
//
// $XDG_RUNTIME_DIR defines the base directory relative to which user-specific non-essential runtime files and
// other file objects (such as sockets, named pipes, ...) should be stored. The directory MUST be owned by the user,
// and he MUST be the only one having read and write access to it. Its Unix access mode MUST be 0700.
//
// TODO(zchee): Avoid use usr.Uid for support the cross-platform compile.
// TODO(zchee): XDG_RUNTIME_DIR seems to change depending on the each distro or init system such as systemd.
// Also In macOS, normal user haven't permission for write to this directory.
func RuntimeDir() string {
	if runtimeDir := os.Getenv("XDG_RUNTIME_DIR"); runtimeDir != "" {
		return runtimeDir
	}
	return filepath.Join(string(filepath.Separator), "run", "user", usr.Uid)
}

func homeDir() string {
	if usrHome != "" {
		return usrHome
	}

	// TODO(zchee): In Windows OS, which of $HOME and these checks has priority?
	if runtime.GOOS == "windows" {
		usrHome = filepath.Join(os.Getenv("HOMEDRIVE"), os.Getenv("HOMEPATH"))
		if usrHome == "" {
			usrHome = os.Getenv("USERPROFILE")
		}
		return usrHome
	}

	usrHome = usr.HomeDir

	return usrHome
}
<|endoftext|>


<commit_before>package xml

import (
	"errors"
	"github.com/crabmusket/gosunspec"
	"github.com/crabmusket/gosunspec/impl"
	"github.com/crabmusket/gosunspec/models/model1"
	"github.com/crabmusket/gosunspec/smdx"
	"github.com/crabmusket/gosunspec/spi"
	_ "log"
	"strconv"
)

type blockAnchor struct {
	device   *DeviceElement
	model    *ModelElement
	index    int
	detached bool
}

type pointAnchor struct {
	position int // position of the point in the model, -1 if it is not present
}

type xmlDriver struct {
}

var ErrNoSuchElement = errors.New("no such element")
var ErrEmptyValue = errors.New("empty value")
var ErrTooManyErrors = errors.New("too many errors")

// Open open a new sunspec.Array which is populated from the specified
// DataElement.
func Open(e *DataElement) (sunspec.Array, error) {
	arr := impl.NewArray()
	for x, _ := range e.Devices {
		if dev, err := OpenDevice(e.Devices[x]); err != nil {
			return nil, err
		} else if err := arr.AddDevice(dev.(spi.DeviceSPI)); err != nil {
			return nil, err
		}
	}
	return arr, nil
}

// OpenDevice opens a new sunspec.Device which is populated from the specified
// DeviceElement.
func OpenDevice(dx *DeviceElement) (sunspec.Device, error) {
	d := impl.NewDevice()
	d.SetAnchor(dx)
	xp := &xmlDriver{}

	//
	// Special case for optional model 1 element in XML representation
	//
	// The SunSpec Model Data Exchange specification allows the model 1
	// element to be omitted from device documents in certain circumstances.
	//
	// In these cases several of the common model elements might be
	// exchanged instead as attributes of the device element. To allow
	// a generic API to bs used to read and write these points, it
	// is simplest to create a detached ModelElement for this case.
	//
	// We don't link the model element to the parent device element unless
	// an attempt is made to write a point that is not duplicated by
	// a device element attribute.
	//
	// The intent is to provided a consistent API across XML and non-XML
	// drivers and to prevent model 1 elements magically appearing in the
	// XML device element merely because an XML device element has been
	// opened but also to ensure that if a write is made into the model 1
	// element, that write is preserved and visible to the consumers
	// of the XML model.
	//
	foundModel1 := false
	for _, mx := range dx.Models {
		foundModel1 = (sunspec.ModelId(mx.Id) == model1.ModelID)
		if foundModel1 {
			break
		}
	}

	models := dx.Models
	if !foundModel1 {
		models = make([]*ModelElement, len(dx.Models)+1)
		models[0] = &ModelElement{Id: model1.ModelID}
		copy(models[1:], dx.Models)
	}

	// iterate through the model elements, creating one new model for each
	// then create a block for each index and add points for each point element
	for _, mx := range models {
		smdx := smdx.GetModel(uint16(mx.Id))
		if smdx == nil {
			continue
		}
		max := uint32(0)
		for _, px := range mx.Points {
			if px.Index > max {
				max = px.Index
			}
		}
		repeats := int(max)
		if len(smdx.Blocks) == 1 {
			repeats -= 1
		}
		m := impl.NewModel(smdx, repeats, xp)
		if err := d.AddModel(m); err != nil {
			return nil, err
		} else {
			for pi, px := range mx.Points {
				bi := int(px.Index)
				if bi > 0 && len(smdx.Blocks) == 1 {
					bi = bi - 1
				}
				b := m.MustBlock(bi)
				if p, err := b.Point(px.Id); err != nil {
					return nil, err
				} else {
					p.(spi.PointSPI).SetAnchor(&pointAnchor{position: pi})
				}
			}
			bi := 0
			m.Do(spi.WithBlockSPI(func(b spi.BlockSPI) {
				detached := mx.Id == model1.ModelID && !foundModel1
				b.SetAnchor(&blockAnchor{device: dx, model: mx, index: bi, detached: detached})
				b.Do(spi.WithPointSPI(func(p spi.PointSPI) {
					if p.Anchor() == nil {
						p.SetAnchor(&pointAnchor{position: -1})
					}
				}))
				bi++
			}))
		}
	}

	if !foundModel1 {
		syncDeviceElementToModel1(dx, d)
	}

	return d, nil
}

//
// For a fake model1 element, initialise it to be consistent with the device element
//
func syncDeviceElementToModel1(dx *DeviceElement, d sunspec.Device) {
	b := d.MustModel(model1.ModelID).MustBlock(0)
	b.MustPoint(model1.SN).SetStringValue(dx.Serial)
	b.MustPoint(model1.Md).SetStringValue(dx.Model)
	b.MustPoint(model1.Mn).SetStringValue(dx.Manufacturer)
	if err := b.Write(model1.SN, model1.Md, model1.Mn); err != nil {
		// we are not expecting this
		panic(err)
	}
}

// CopyArray copies an existing SunSpec Array into a new SunSpec Array and an
// XML DataElement. Operations on the returned SunSpec Array edit the
// returned DataElement.
func CopyArray(a sunspec.Array) (sunspec.Array, *DataElement) {
	arr := impl.NewArray()
	x := &DataElement{
		Version: "1",
	}

	a.Do(func(d sunspec.Device) {
		da, dx := CopyDevice(d)
		x.Devices = append(x.Devices, dx)
		if err := arr.AddDevice(da.(spi.DeviceSPI)); err != nil {
			// we are not expecting this to happen
			panic(err)
		}
	})
	return arr, x
}

// CopyDevice copies an existing SunSpec Device into a new SunSpec Device and an
// XML DeviceElement. Operations on the returned SunSpec Device edit the
// returned DeviceElement.
func CopyDevice(d sunspec.Device) (sunspec.Device, *DeviceElement) {
	dc := impl.NewDevice()
	dx := &DeviceElement{}
	xp := &xmlDriver{}

	modelElements := map[sunspec.ModelId]*ModelElement{}

	newModelElement := func(id sunspec.ModelId) *ModelElement {

		// we only use labels when there is ambiguity

		var mx *ModelElement
		var ok bool
		if mx, ok = modelElements[id]; ok {
			if mx.Index == 0 {
				mx.Index = 1
			}
			mx = &ModelElement{Id: id, Index: mx.Index + 1}
		} else {
			mx = &ModelElement{Id: id, Index: 0}
		}
		modelElements[id] = mx
		return mx
	}

	d.Do(func(m sunspec.Model) {
		smdx := smdx.GetModel(uint16(m.Id()))

		repeatOnly := m.Blocks() > 1 && len(smdx.Blocks) == 1
		mc := impl.NewModel(smdx, m.Blocks()-1, xp)
		mx := newModelElement(m.Id())

		dx.Models = append(dx.Models, mx)

		for i := 0; i < m.Blocks(); i++ {
			b := m.MustBlock(i).(spi.BlockSPI)
			bc := mc.MustBlock(i).(spi.BlockSPI)

			x := i
			if repeatOnly {
				x = x + 1
			}
			anchor := &blockAnchor{
				device: dx,
				model:  mx,
				index:  x,
			}
			bc.SetAnchor(anchor)
			points, _ := bc.Plan()
			for _, pc := range points {
				pa := &pointAnchor{position: -1}
				pc.SetAnchor(pa)
				p := b.MustPoint(pc.Id())
				if p.Error() == nil {
					pc.SetValue(p.Value())
				}
			}

			// write the copied values into the copied block

			if err := bc.Write(); err != nil {
				// not expecting this to happen
				panic(err)
			}
		}
		dc.AddModel(mc)
	})
	return dc, dx
}

func (phys *xmlDriver) Read(b spi.BlockSPI, pointIds ...string) error {
	errCount := 0
	var firstError error
	ba := b.Anchor().(*blockAnchor)
	if points, err := b.Plan(pointIds...); err != nil {
		return err
	} else {
		for _, p := range points {
			recordError := func(e error) {
				if firstError == nil {
					firstError = e
				}
				p.SetError(e)
				errCount++
				// log.Printf("point error: id=%s: %v", p.Id(), e)
			}

			pa := p.Anchor().(*pointAnchor)
			if pa.position < 0 {
				recordError(ErrNoSuchElement)
				continue
			}
			px := ba.model.Points[pa.position]
			if len(px.Value) == 0 {
				recordError(ErrEmptyValue)
				continue
			}
			sfp := p.ScaleFactorPoint()
			v := px.Value
			if sfp != nil {
				vsf := sunspec.ScaleFactor(px.ScaleFactor)
				if sfp.Error() != nil {
					sfp.SetScaleFactor(vsf)
				}
				if vi, err := strconv.Atoi(v); err != nil {
					recordError(err)
					continue
				} else {
					for vsf > sfp.ScaleFactor() {
						vi = vi * 10
						vsf--
					}
					for vsf < sfp.ScaleFactor() {
						vi = vi / 10
						vsf++
					}
					v = strconv.Itoa(vi)
				}
			}
			if err := p.UnmarshalXML(v); err != nil {
				recordError(err)
				continue
			}

			// need to values with scaling factors match the scaling factor or else
			// adjust the value to be consistent with the current scaling factor value
		}
	}
	if errCount > 0 {
		return firstError
	} else {
		return nil
	}
}

func (phys *xmlDriver) Write(b spi.BlockSPI, pointIds ...string) error {

	write := map[string]bool{}
	if len(pointIds) == 0 {
		b.Do(func(p sunspec.Point) {
			write[p.Id()] = true
		})
	} else {
		for _, id := range pointIds {
			write[id] = true
		}
	}

	ba := b.Anchor().(*blockAnchor)
	b.Do(spi.WithPointSPI(func(p spi.PointSPI) {
		if !write[p.Id()] {
			return
		}
		pa := p.Anchor().(*pointAnchor)
		var px *PointElement
		if pa.position == -1 && p.Error() == nil {
			// add a new point to the model element
			px = &PointElement{Id: p.Id(), Index: uint32(ba.index)}
			pa.position = len(ba.model.Points)
			ba.model.Points = append(ba.model.Points, px)
		} else if pa.position >= 0 && p.Error() != nil {
			// remove a point from the model element
			swap := b.MustPoint(ba.model.Points[len(ba.model.Points)-1].Id).(spi.PointSPI).Anchor().(*pointAnchor)
			ba.model.Points[pa.position] = ba.model.Points[swap.position]
			swap.position = pa.position
			ba.model.Points = ba.model.Points[0 : len(ba.model.Points)-1]
			px = nil
		} else if pa.position >= 0 {
			// update the existing point
			px = ba.model.Points[pa.position]
		}
		if px != nil {
			px.Value = p.MarshalXML()
			if sfp := p.ScaleFactorPoint(); sfp != nil {
				px.ScaleFactor = int16(sfp.ScaleFactor())
			}
			// we could put the description and units (from the SMDX model) in here, but
			// we probably need a configuration option to say whether this is necessary
			// or not
		}
	}))

	// A special case required by a partial redundancy of the XML model.
	//
	// Writes into the model 1 points that also appear in the device element
	// as attributes are mirrored into the device element attributes.
	//
	// In all other cases, we need to link a detached model element to the
	// parent so that these updates are visible to consumers of the XML model.
	//
	// See also: OpenDevice

	if ba.model.Id == model1.ModelID {

		count := 0
		for pid, _ := range write {
			switch pid {
			case model1.SN, model1.Md, model1.Mn:
				// synchronize device element with writes into model
				p := b.MustPoint(pid)
				var v string
				if p.Error() == nil {
					v = p.StringValue()
				} else {
					v = ""
				}
				switch pid {
				case model1.SN:
					ba.device.Serial = v
				case model1.Md:
					ba.device.Model = v
				case model1.Mn:
					ba.device.Manufacturer = v
				}
			default:
				// count writes into other points
				count++
			}
		}

		// If there is a write to a point that isn't encoded in the device
		// element and the model 1 element is currently detached from the device
		// (because it wasn't present on open) then fix that by attaching the
		// model element to the parent device now.

		if count > 0 && ba.detached {
			models := make([]*ModelElement, len(ba.device.Models)+1)
			models[0] = ba.model
			copy(models[1:], ba.device.Models)
			ba.detached = false
			ba.device.Models = models
		}
	}

	return nil
}
<commit_msg>ErrNoSuchElement any other kind of error is more interesting than ErrNoSuchElement.<commit_after>package xml

import (
	"errors"
	"github.com/crabmusket/gosunspec"
	"github.com/crabmusket/gosunspec/impl"
	"github.com/crabmusket/gosunspec/models/model1"
	"github.com/crabmusket/gosunspec/smdx"
	"github.com/crabmusket/gosunspec/spi"
	_ "log"
	"strconv"
)

type blockAnchor struct {
	device   *DeviceElement
	model    *ModelElement
	index    int
	detached bool
}

type pointAnchor struct {
	position int // position of the point in the model, -1 if it is not present
}

type xmlDriver struct {
}

var ErrNoSuchElement = errors.New("no such element")
var ErrEmptyValue = errors.New("empty value")
var ErrTooManyErrors = errors.New("too many errors")

// Open open a new sunspec.Array which is populated from the specified
// DataElement.
func Open(e *DataElement) (sunspec.Array, error) {
	arr := impl.NewArray()
	for x, _ := range e.Devices {
		if dev, err := OpenDevice(e.Devices[x]); err != nil {
			return nil, err
		} else if err := arr.AddDevice(dev.(spi.DeviceSPI)); err != nil {
			return nil, err
		}
	}
	return arr, nil
}

// OpenDevice opens a new sunspec.Device which is populated from the specified
// DeviceElement.
func OpenDevice(dx *DeviceElement) (sunspec.Device, error) {
	d := impl.NewDevice()
	d.SetAnchor(dx)
	xp := &xmlDriver{}

	//
	// Special case for optional model 1 element in XML representation
	//
	// The SunSpec Model Data Exchange specification allows the model 1
	// element to be omitted from device documents in certain circumstances.
	//
	// In these cases several of the common model elements might be
	// exchanged instead as attributes of the device element. To allow
	// a generic API to bs used to read and write these points, it
	// is simplest to create a detached ModelElement for this case.
	//
	// We don't link the model element to the parent device element unless
	// an attempt is made to write a point that is not duplicated by
	// a device element attribute.
	//
	// The intent is to provided a consistent API across XML and non-XML
	// drivers and to prevent model 1 elements magically appearing in the
	// XML device element merely because an XML device element has been
	// opened but also to ensure that if a write is made into the model 1
	// element, that write is preserved and visible to the consumers
	// of the XML model.
	//
	foundModel1 := false
	for _, mx := range dx.Models {
		foundModel1 = (sunspec.ModelId(mx.Id) == model1.ModelID)
		if foundModel1 {
			break
		}
	}

	models := dx.Models
	if !foundModel1 {
		models = make([]*ModelElement, len(dx.Models)+1)
		models[0] = &ModelElement{Id: model1.ModelID}
		copy(models[1:], dx.Models)
	}

	// iterate through the model elements, creating one new model for each
	// then create a block for each index and add points for each point element
	for _, mx := range models {
		smdx := smdx.GetModel(uint16(mx.Id))
		if smdx == nil {
			continue
		}
		max := uint32(0)
		for _, px := range mx.Points {
			if px.Index > max {
				max = px.Index
			}
		}
		repeats := int(max)
		if len(smdx.Blocks) == 1 {
			repeats -= 1
		}
		m := impl.NewModel(smdx, repeats, xp)
		if err := d.AddModel(m); err != nil {
			return nil, err
		} else {
			for pi, px := range mx.Points {
				bi := int(px.Index)
				if bi > 0 && len(smdx.Blocks) == 1 {
					bi = bi - 1
				}
				b := m.MustBlock(bi)
				if p, err := b.Point(px.Id); err != nil {
					return nil, err
				} else {
					p.(spi.PointSPI).SetAnchor(&pointAnchor{position: pi})
				}
			}
			bi := 0
			m.Do(spi.WithBlockSPI(func(b spi.BlockSPI) {
				detached := mx.Id == model1.ModelID && !foundModel1
				b.SetAnchor(&blockAnchor{device: dx, model: mx, index: bi, detached: detached})
				b.Do(spi.WithPointSPI(func(p spi.PointSPI) {
					if p.Anchor() == nil {
						p.SetAnchor(&pointAnchor{position: -1})
					}
				}))
				bi++
			}))
		}
	}

	if !foundModel1 {
		syncDeviceElementToModel1(dx, d)
	}

	return d, nil
}

//
// For a fake model1 element, initialise it to be consistent with the device element
//
func syncDeviceElementToModel1(dx *DeviceElement, d sunspec.Device) {
	b := d.MustModel(model1.ModelID).MustBlock(0)
	b.MustPoint(model1.SN).SetStringValue(dx.Serial)
	b.MustPoint(model1.Md).SetStringValue(dx.Model)
	b.MustPoint(model1.Mn).SetStringValue(dx.Manufacturer)
	if err := b.Write(model1.SN, model1.Md, model1.Mn); err != nil {
		// we are not expecting this
		panic(err)
	}
}

// CopyArray copies an existing SunSpec Array into a new SunSpec Array and an
// XML DataElement. Operations on the returned SunSpec Array edit the
// returned DataElement.
func CopyArray(a sunspec.Array) (sunspec.Array, *DataElement) {
	arr := impl.NewArray()
	x := &DataElement{
		Version: "1",
	}

	a.Do(func(d sunspec.Device) {
		da, dx := CopyDevice(d)
		x.Devices = append(x.Devices, dx)
		if err := arr.AddDevice(da.(spi.DeviceSPI)); err != nil {
			// we are not expecting this to happen
			panic(err)
		}
	})
	return arr, x
}

// CopyDevice copies an existing SunSpec Device into a new SunSpec Device and an
// XML DeviceElement. Operations on the returned SunSpec Device edit the
// returned DeviceElement.
func CopyDevice(d sunspec.Device) (sunspec.Device, *DeviceElement) {
	dc := impl.NewDevice()
	dx := &DeviceElement{}
	xp := &xmlDriver{}

	modelElements := map[sunspec.ModelId]*ModelElement{}

	newModelElement := func(id sunspec.ModelId) *ModelElement {

		// we only use labels when there is ambiguity

		var mx *ModelElement
		var ok bool
		if mx, ok = modelElements[id]; ok {
			if mx.Index == 0 {
				mx.Index = 1
			}
			mx = &ModelElement{Id: id, Index: mx.Index + 1}
		} else {
			mx = &ModelElement{Id: id, Index: 0}
		}
		modelElements[id] = mx
		return mx
	}

	d.Do(func(m sunspec.Model) {
		smdx := smdx.GetModel(uint16(m.Id()))

		repeatOnly := m.Blocks() > 1 && len(smdx.Blocks) == 1
		mc := impl.NewModel(smdx, m.Blocks()-1, xp)
		mx := newModelElement(m.Id())

		dx.Models = append(dx.Models, mx)

		for i := 0; i < m.Blocks(); i++ {
			b := m.MustBlock(i).(spi.BlockSPI)
			bc := mc.MustBlock(i).(spi.BlockSPI)

			x := i
			if repeatOnly {
				x = x + 1
			}
			anchor := &blockAnchor{
				device: dx,
				model:  mx,
				index:  x,
			}
			bc.SetAnchor(anchor)
			points, _ := bc.Plan()
			for _, pc := range points {
				pa := &pointAnchor{position: -1}
				pc.SetAnchor(pa)
				p := b.MustPoint(pc.Id())
				if p.Error() == nil {
					pc.SetValue(p.Value())
				}
			}

			// write the copied values into the copied block

			if err := bc.Write(); err != nil {
				// not expecting this to happen
				panic(err)
			}
		}
		dc.AddModel(mc)
	})
	return dc, dx
}

func (phys *xmlDriver) Read(b spi.BlockSPI, pointIds ...string) error {
	errCount := 0
	var firstError error
	ba := b.Anchor().(*blockAnchor)
	if points, err := b.Plan(pointIds...); err != nil {
		return err
	} else {
		for _, p := range points {
			recordError := func(e error) {
				if firstError == nil || firstError == ErrNoSuchElement {
					firstError = e
				}
				p.SetError(e)
				errCount++
				// log.Printf("point error: id=%s: %v", p.Id(), e)
			}

			pa := p.Anchor().(*pointAnchor)
			if pa.position < 0 {
				recordError(ErrNoSuchElement)
				continue
			}
			px := ba.model.Points[pa.position]
			if len(px.Value) == 0 {
				recordError(ErrEmptyValue)
				continue
			}
			sfp := p.ScaleFactorPoint()
			v := px.Value
			if sfp != nil {
				vsf := sunspec.ScaleFactor(px.ScaleFactor)
				if sfp.Error() != nil {
					sfp.SetScaleFactor(vsf)
				}
				if vi, err := strconv.Atoi(v); err != nil {
					recordError(err)
					continue
				} else {
					for vsf > sfp.ScaleFactor() {
						vi = vi * 10
						vsf--
					}
					for vsf < sfp.ScaleFactor() {
						vi = vi / 10
						vsf++
					}
					v = strconv.Itoa(vi)
				}
			}
			if err := p.UnmarshalXML(v); err != nil {
				recordError(err)
				continue
			}

			// need to values with scaling factors match the scaling factor or else
			// adjust the value to be consistent with the current scaling factor value
		}
	}
	if errCount > 0 {
		return firstError
	} else {
		return nil
	}
}

func (phys *xmlDriver) Write(b spi.BlockSPI, pointIds ...string) error {

	write := map[string]bool{}
	if len(pointIds) == 0 {
		b.Do(func(p sunspec.Point) {
			write[p.Id()] = true
		})
	} else {
		for _, id := range pointIds {
			write[id] = true
		}
	}

	ba := b.Anchor().(*blockAnchor)
	b.Do(spi.WithPointSPI(func(p spi.PointSPI) {
		if !write[p.Id()] {
			return
		}
		pa := p.Anchor().(*pointAnchor)
		var px *PointElement
		if pa.position == -1 && p.Error() == nil {
			// add a new point to the model element
			px = &PointElement{Id: p.Id(), Index: uint32(ba.index)}
			pa.position = len(ba.model.Points)
			ba.model.Points = append(ba.model.Points, px)
		} else if pa.position >= 0 && p.Error() != nil {
			// remove a point from the model element
			swap := b.MustPoint(ba.model.Points[len(ba.model.Points)-1].Id).(spi.PointSPI).Anchor().(*pointAnchor)
			ba.model.Points[pa.position] = ba.model.Points[swap.position]
			swap.position = pa.position
			ba.model.Points = ba.model.Points[0 : len(ba.model.Points)-1]
			px = nil
		} else if pa.position >= 0 {
			// update the existing point
			px = ba.model.Points[pa.position]
		}
		if px != nil {
			px.Value = p.MarshalXML()
			if sfp := p.ScaleFactorPoint(); sfp != nil {
				px.ScaleFactor = int16(sfp.ScaleFactor())
			}
			// we could put the description and units (from the SMDX model) in here, but
			// we probably need a configuration option to say whether this is necessary
			// or not
		}
	}))

	// A special case required by a partial redundancy of the XML model.
	//
	// Writes into the model 1 points that also appear in the device element
	// as attributes are mirrored into the device element attributes.
	//
	// In all other cases, we need to link a detached model element to the
	// parent so that these updates are visible to consumers of the XML model.
	//
	// See also: OpenDevice

	if ba.model.Id == model1.ModelID {

		count := 0
		for pid, _ := range write {
			switch pid {
			case model1.SN, model1.Md, model1.Mn:
				// synchronize device element with writes into model
				p := b.MustPoint(pid)
				var v string
				if p.Error() == nil {
					v = p.StringValue()
				} else {
					v = ""
				}
				switch pid {
				case model1.SN:
					ba.device.Serial = v
				case model1.Md:
					ba.device.Model = v
				case model1.Mn:
					ba.device.Manufacturer = v
				}
			default:
				// count writes into other points
				count++
			}
		}

		// If there is a write to a point that isn't encoded in the device
		// element and the model 1 element is currently detached from the device
		// (because it wasn't present on open) then fix that by attaching the
		// model element to the parent device now.

		if count > 0 && ba.detached {
			models := make([]*ModelElement, len(ba.device.Models)+1)
			models[0] = ba.model
			copy(models[1:], ba.device.Models)
			ba.detached = false
			ba.device.Models = models
		}
	}

	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"testing"
)

func TestCommenterSessionUpdateBasics(t *testing.T) {
	failTestOnError(t, setupTestEnv())

	commenterToken, _ := commenterTokenNew()

	if err := commenterSessionUpdate(commenterToken, "temp-commenter-hex"); err != nil {
		t.Errorf("unexpected error updating commenter session: %v", err)
		return
	}
}

func TestCommenterSessionUpdateEmpty(t *testing.T) {
	failTestOnError(t, setupTestEnv())

	if err := commenterSessionUpdate("", "temp-commenter-hex"); err == nil {
		t.Errorf("expected error not found when updating with empty commenterToken")
		return
	}
}
<commit_msg>commenter_session_update_test.go: verify update<commit_after>package main

import (
	"testing"
)

func TestCommenterSessionUpdateBasics(t *testing.T) {
	failTestOnError(t, setupTestEnv())

	commenterToken, _ := commenterTokenNew()

	if err := commenterSessionUpdate(commenterToken, "temp-commenter-hex"); err != nil {
		t.Errorf("unexpected error updating commenter session: %v", err)
		return
	}

	statement := `
		SELECT commenterHex
		FROM commenterSessions
		WHERE commenterToken = $1;
	`
	row := db.QueryRow(statement, commenterToken)

	var commenterHex string
	if err := row.Scan(&commenterHex); err != nil {
		t.Errorf("error scanning commenterHex: %v", err)
		return
	}

	if commenterHex != "temp-commenter-hex" {
		t.Errorf("expected commenterHex=temp-commenter-hex got commenterHex=%s", commenterHex)
		return
	}
}

func TestCommenterSessionUpdateEmpty(t *testing.T) {
	failTestOnError(t, setupTestEnv())

	if err := commenterSessionUpdate("", "temp-commenter-hex"); err == nil {
		t.Errorf("expected error not found when updating with empty commenterToken")
		return
	}
}
<|endoftext|>


<commit_before>package background

import (
	"image"
	"os/user"
	"path/filepath"
	"syscall"
	"unsafe"
)

const (
	spiSETDESKWALLPAPER = 0x14
	spifUPDATEINIFILE   = 0x2
)

var (
	user32 = syscall.MustLoadDLL("user32.dll")
	proc   = user32.MustFindProc("SystemParametersInfoW")
)

// Set the background on windows.
func Set(img image.Image) error {
	// Get the absolute path of the directory.
	usr, err := user.Current()
	if err != nil {
		return err
	}
	imgPath := filepath.Join(usr.HomeDir, "AppData", "Roaming", "Himawari")

	// Create the file.
	if err := createFile(img, imgPath); err != nil {
		return err
	}

	// Set the background, hoping it worked.
	proc.Call(
		spiSETDESKWALLPAPER,
		0,
		uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(path))),
		spifUPDATEINIFILE,
	)
	return nil
}
<commit_msg>Fix stupid windows bugs.<commit_after>package background

import (
	"image"
	"os/user"
	"path/filepath"
	"syscall"
	"unsafe"
)

const (
	spiSETDESKWALLPAPER = 0x14
	spifUPDATEINIFILE   = 0x2
)

var (
	user32 = syscall.MustLoadDLL("user32.dll")
	proc   = user32.MustFindProc("SystemParametersInfoW")
)

// Set the background on windows.
func Set(img image.Image) error {
	// Get the absolute path of the directory.
	usr, err := user.Current()
	if err != nil {
		return err
	}
	imgPath := filepath.Join(usr.HomeDir, "AppData", "Roaming", "Himawari", "background.png")

	// Create the file.
	if err := createFile(img, imgPath); err != nil {
		return err
	}

	// Set the background, hoping it worked.
	proc.Call(
		spiSETDESKWALLPAPER,
		0,
		uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(imgPath))),
		spifUPDATEINIFILE,
	)
	return nil
}
<|endoftext|>


<commit_before>package locator

import (
	"fmt"
	"go/ast"
	"go/parser"
	"go/token"
	"os"
	"path"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"

	"github.com/maxbrunsfeld/counterfeiter/model"
)

func GetInterfaceFromFilePath(interfaceName, filePath string) (*model.InterfaceToFake, error) {
	dirPath, err := getDir(filePath)
	if err != nil {
		return nil, err
	}

	importPath, err := importPathForDirPath(dirPath)
	if err != nil {
		return nil, err
	}

	vendorPaths, err := vendorPathsForDirPath(dirPath)
	if err != nil {
		return nil, err
	}

	return GetInterfaceFromImportPath(interfaceName, importPath, vendorPaths...)
}

func GetInterfaceFromImportPath(interfaceName, importPath string, vendorPaths ...string) (*model.InterfaceToFake, error) {
	dirPath, err := dirPathForImportPath(importPath, vendorPaths)
	if err != nil {
		return nil, err
	}

	packages, err := packagesForDirPath(dirPath)
	if err != nil {
		return nil, err
	}

	for _, pkg := range packages {
		iface, file, isFunction, err := findInterface(pkg, interfaceName)
		if err != nil {
			return nil, err
		}

		if iface != nil {
			typesFound := getTypeNames(pkg)
			importSpecs := getImports(file)

			pkgImport := pkg.Name
			if strings.HasSuffix(importPath, pkg.Name) {
				pkgImport = "xyz123"
			}

			var methods []model.Method
			var err error
			switch iface.(type) {
			case *ast.InterfaceType:
				interfaceNode := iface.(*ast.InterfaceType)
				methods, err = methodsForInterface(interfaceNode, importPath, pkgImport, importSpecs, typesFound, vendorPaths)
			case *ast.FuncType:
				funcNode := iface.(*ast.FuncType)
				methods, err = methodsForFunction(funcNode, interfaceName, pkgImport, importSpecs, typesFound)
			default:
				err = fmt.Errorf("cannot generate a counterfeit for a '%T'", iface)
			}

			if err != nil {
				return nil, err
			}

			importSpecs[pkgImport] = &ast.ImportSpec{
				Name: &ast.Ident{Name: pkgImport},
				Path: &ast.BasicLit{
					Kind:  token.STRING,
					Value: strconv.Quote(importPath),
				},
			}

			return &model.InterfaceToFake{
				Name:                   interfaceName,
				Methods:                methods,
				ImportPath:             importPath,
				PackageName:            pkg.Name,
				RepresentedByInterface: !isFunction,
			}, nil
		}
	}

	return nil, fmt.Errorf("Could not find interface '%s'", interfaceName)
}

func getDir(path string) (string, error) {
	stat, err := os.Stat(path)
	if err != nil {
		return "", err
	}

	if !stat.IsDir() {
		path = filepath.Dir(path)
	}

	return path, nil
}

func findImportPath(importSpecs map[string]*ast.ImportSpec, alias string) string {
	if importSpec, ok := importSpecs[alias]; ok {
		return strings.Trim(importSpec.Path.Value, `"`)
	}
	return ""
}

func dirPathForImportPath(importPath string, vendorPaths []string) (string, error) {
	for _, srcPath := range append(vendorPaths, goSourcePaths()...) {
		dirPath := filepath.Join(srcPath, filepath.Clean(importPath))
		stat, err := os.Stat(dirPath)
		if err == nil && stat.IsDir() {
			return dirPath, nil
		}
	}

	return "", fmt.Errorf("Package '%s' not found on GOPATH", importPath)
}

func importPathForDirPath(sourcePath string) (string, error) {
	sourcePath, err := filepath.Abs(sourcePath)
	if err != nil {
		return "", err
	}

	for _, goSrcPath := range goSourcePaths() {
		if strings.HasPrefix(sourcePath, goSrcPath) {
			return sourcePath[len(goSrcPath)+1:], nil
		}
	}

	return "", fmt.Errorf("Path '%s' is not on GOPATH", sourcePath)
}

func vendorPathsForDirPath(dirPath string) ([]string, error) {
	dirPath, err := filepath.Abs(dirPath)
	if err != nil {
		return nil, err
	}

	vendorPaths := []string{}
	for _, goSrcPath := range goSourcePaths() {
		for strings.HasPrefix(dirPath, goSrcPath) {
			vendorPath := filepath.Join(dirPath, "vendor")
			stat, err := os.Stat(vendorPath)
			if err == nil && stat.IsDir() {
				vendorPaths = append(vendorPaths, vendorPath)
			}
			dirPath = filepath.Dir(dirPath)
		}
	}

	return vendorPaths, nil
}

func goSourcePaths() []string {
	result := []string{}
	for _, path := range strings.Split(os.Getenv("GOPATH"), ":") {
		result = append(result, filepath.Join(path, "src"))
	}
	result = append(result, filepath.Join(runtime.GOROOT(), "src"))
	return result
}

func packagesForDirPath(path string) (map[string]*ast.Package, error) {
	return parser.ParseDir(token.NewFileSet(), path, nil, parser.AllErrors)
}

func findInterface(pkg *ast.Package, interfaceName string) (ast.Node, *ast.File, bool, error) {
	var file *ast.File
	var iface ast.Node
	var err error
	var isFunction bool

	for _, f := range pkg.Files {
		ast.Inspect(f, func(node ast.Node) bool {
			typeSpec, ok := node.(*ast.TypeSpec)
			if !ok || typeSpec.Name.Name != interfaceName {
				return true
			}

			switch typeSpec.Type.(type) {
			case *ast.InterfaceType:
				file = f
				iface = typeSpec.Type
			case *ast.FuncType:
				file = f
				isFunction = true
				iface = typeSpec.Type
			default:
				err = fmt.Errorf("Name '%s' does not refer to an interface", interfaceName)
			}
			return false
		})

		if iface != nil {
			break
		}
	}

	return iface, file, isFunction, err
}

func getImports(file *ast.File) map[string]*ast.ImportSpec {
	result := map[string]*ast.ImportSpec{}
	ast.Inspect(file, func(node ast.Node) bool {
		if importSpec, ok := node.(*ast.ImportSpec); ok {
			var alias string
			if importSpec.Name == nil {
				alias = path.Base(strings.Trim(importSpec.Path.Value, `"`))
			} else {
				alias = importSpec.Name.Name
			}
			result[alias] = importSpec
		}
		return true
	})
	return result
}

func getTypeNames(pkg *ast.Package) map[string]bool {
	result := make(map[string]bool)
	ast.Inspect(pkg, func(node ast.Node) bool {
		if typeSpec, ok := node.(*ast.TypeSpec); ok {
			if typeSpec.Name != nil {
				result[typeSpec.Name.Name] = true
			}
		}
		return true
	})
	return result
}
<commit_msg>Fix hard-coded unix-style path separator<commit_after>package locator

import (
	"fmt"
	"go/ast"
	"go/parser"
	"go/token"
	"os"
	"path"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"

	"github.com/maxbrunsfeld/counterfeiter/model"
)

func GetInterfaceFromFilePath(interfaceName, filePath string) (*model.InterfaceToFake, error) {
	dirPath, err := getDir(filePath)
	if err != nil {
		return nil, err
	}

	importPath, err := importPathForDirPath(dirPath)
	if err != nil {
		return nil, err
	}

	vendorPaths, err := vendorPathsForDirPath(dirPath)
	if err != nil {
		return nil, err
	}

	return GetInterfaceFromImportPath(interfaceName, importPath, vendorPaths...)
}

func GetInterfaceFromImportPath(interfaceName, importPath string, vendorPaths ...string) (*model.InterfaceToFake, error) {
	dirPath, err := dirPathForImportPath(importPath, vendorPaths)
	if err != nil {
		return nil, err
	}

	packages, err := packagesForDirPath(dirPath)
	if err != nil {
		return nil, err
	}

	for _, pkg := range packages {
		iface, file, isFunction, err := findInterface(pkg, interfaceName)
		if err != nil {
			return nil, err
		}

		if iface != nil {
			typesFound := getTypeNames(pkg)
			importSpecs := getImports(file)

			pkgImport := pkg.Name
			if strings.HasSuffix(importPath, pkg.Name) {
				pkgImport = "xyz123"
			}

			var methods []model.Method
			var err error
			switch iface.(type) {
			case *ast.InterfaceType:
				interfaceNode := iface.(*ast.InterfaceType)
				methods, err = methodsForInterface(interfaceNode, importPath, pkgImport, importSpecs, typesFound, vendorPaths)
			case *ast.FuncType:
				funcNode := iface.(*ast.FuncType)
				methods, err = methodsForFunction(funcNode, interfaceName, pkgImport, importSpecs, typesFound)
			default:
				err = fmt.Errorf("cannot generate a counterfeit for a '%T'", iface)
			}

			if err != nil {
				return nil, err
			}

			importSpecs[pkgImport] = &ast.ImportSpec{
				Name: &ast.Ident{Name: pkgImport},
				Path: &ast.BasicLit{
					Kind:  token.STRING,
					Value: strconv.Quote(importPath),
				},
			}

			return &model.InterfaceToFake{
				Name:                   interfaceName,
				Methods:                methods,
				ImportPath:             importPath,
				PackageName:            pkg.Name,
				RepresentedByInterface: !isFunction,
			}, nil
		}
	}

	return nil, fmt.Errorf("Could not find interface '%s'", interfaceName)
}

func getDir(path string) (string, error) {
	stat, err := os.Stat(path)
	if err != nil {
		return "", err
	}

	if !stat.IsDir() {
		path = filepath.Dir(path)
	}

	return path, nil
}

func findImportPath(importSpecs map[string]*ast.ImportSpec, alias string) string {
	if importSpec, ok := importSpecs[alias]; ok {
		return strings.Trim(importSpec.Path.Value, `"`)
	}
	return ""
}

func dirPathForImportPath(importPath string, vendorPaths []string) (string, error) {
	for _, srcPath := range append(vendorPaths, goSourcePaths()...) {
		dirPath := filepath.Join(srcPath, filepath.Clean(importPath))
		stat, err := os.Stat(dirPath)
		if err == nil && stat.IsDir() {
			return dirPath, nil
		}
	}

	return "", fmt.Errorf("Package '%s' not found on GOPATH", importPath)
}

func importPathForDirPath(sourcePath string) (string, error) {
	sourcePath, err := filepath.Abs(sourcePath)
	if err != nil {
		return "", err
	}

	for _, goSrcPath := range goSourcePaths() {
		if strings.HasPrefix(sourcePath, goSrcPath) {
			return sourcePath[len(goSrcPath)+1:], nil
		}
	}

	return "", fmt.Errorf("Path '%s' is not on GOPATH", sourcePath)
}

func vendorPathsForDirPath(dirPath string) ([]string, error) {
	dirPath, err := filepath.Abs(dirPath)
	if err != nil {
		return nil, err
	}

	vendorPaths := []string{}
	for _, goSrcPath := range goSourcePaths() {
		for strings.HasPrefix(dirPath, goSrcPath) {
			vendorPath := filepath.Join(dirPath, "vendor")
			stat, err := os.Stat(vendorPath)
			if err == nil && stat.IsDir() {
				vendorPaths = append(vendorPaths, vendorPath)
			}
			dirPath = filepath.Dir(dirPath)
		}
	}

	return vendorPaths, nil
}

func goSourcePaths() []string {
	result := []string{}
	separator := string(os.PathListSeparator)
	for _, path := range strings.Split(os.Getenv("GOPATH"), separator) {
		result = append(result, filepath.Join(path, "src"))
	}
	result = append(result, filepath.Join(runtime.GOROOT(), "src"))
	return result
}

func packagesForDirPath(path string) (map[string]*ast.Package, error) {
	return parser.ParseDir(token.NewFileSet(), path, nil, parser.AllErrors)
}

func findInterface(pkg *ast.Package, interfaceName string) (ast.Node, *ast.File, bool, error) {
	var file *ast.File
	var iface ast.Node
	var err error
	var isFunction bool

	for _, f := range pkg.Files {
		ast.Inspect(f, func(node ast.Node) bool {
			typeSpec, ok := node.(*ast.TypeSpec)
			if !ok || typeSpec.Name.Name != interfaceName {
				return true
			}

			switch typeSpec.Type.(type) {
			case *ast.InterfaceType:
				file = f
				iface = typeSpec.Type
			case *ast.FuncType:
				file = f
				isFunction = true
				iface = typeSpec.Type
			default:
				err = fmt.Errorf("Name '%s' does not refer to an interface", interfaceName)
			}
			return false
		})

		if iface != nil {
			break
		}
	}

	return iface, file, isFunction, err
}

func getImports(file *ast.File) map[string]*ast.ImportSpec {
	result := map[string]*ast.ImportSpec{}
	ast.Inspect(file, func(node ast.Node) bool {
		if importSpec, ok := node.(*ast.ImportSpec); ok {
			var alias string
			if importSpec.Name == nil {
				alias = path.Base(strings.Trim(importSpec.Path.Value, `"`))
			} else {
				alias = importSpec.Name.Name
			}
			result[alias] = importSpec
		}
		return true
	})
	return result
}

func getTypeNames(pkg *ast.Package) map[string]bool {
	result := make(map[string]bool)
	ast.Inspect(pkg, func(node ast.Node) bool {
		if typeSpec, ok := node.(*ast.TypeSpec); ok {
			if typeSpec.Name != nil {
				result[typeSpec.Name.Name] = true
			}
		}
		return true
	})
	return result
}
<|endoftext|>


<commit_before>// The handler of the logger.
package handler

import (
	"errors"
	"io"
	"os"
	"path/filepath"
	"regexp"
	"sort"
	"sync"
	"time"

	"github.com/xgfone/go-tools/file"
)

const (
	DAY_FMT = "2006-01-02"

	FILE_MODE = os.O_APPEND | os.O_CREATE | os.O_WRONLY
	FILE_PERM = os.ModePerm
)

var (
	dayRE       = regexp.MustCompile(`^\d{4}-\d{2}-\d{2}(\.\w+)?$`)
	day   int64 = 3600 * 24

	time2fmt = map[int64]string{
		day: DAY_FMT,
	}
)

var (
	ErrFileNotOpen = errors.New("The file is not opened")
)

// A file handler based on the timed rotating, like
// `logging.handlers.TimedRotatingFileHandler` in Python.
// Now only support the rotation by day.
type TimedRotatingFile struct {
	sync.Mutex
	w io.WriteCloser

	filename    string
	backupCount int
	interval    int64
	when        int64
	rotatorAt   int64
	extRE       *regexp.Regexp
}

// Create a new TimedRotatingFile.
func NewTimedRotatingFile(filename string) *TimedRotatingFile {
	filename, _ = filepath.Abs(filename)
	t := TimedRotatingFile{filename: filename, when: day, extRE: dayRE}
	t.SetBackupCount(31).SetInterval(1)
	if err := t.open(); err != nil {
		panic(err)
	}
	return &t
}

// Write the string data into the file, which may rotate the file if necessary.
func (self *TimedRotatingFile) WriteString(data string) (n int, err error) {
	return self.Write([]byte(data))
}

// Write the byte slice data into the file, which may rotate the file if necessary.
func (self *TimedRotatingFile) Write(data []byte) (n int, err error) {
	self.Lock()
	defer self.Unlock()

	if self.w == nil {
		err = ErrFileNotOpen
		return
	}

	if self.shouldRollover() {
		if err = self.doRollover(); err != nil {
			return
		}
	}

	return self.w.Write(data)
}

// Set the number of the backup file. The default is 31.
func (self *TimedRotatingFile) SetBackupCount(num int) *TimedRotatingFile {
	self.backupCount = num
	return self
}

// Set the interval day number to rotate. The default is 1.
func (self *TimedRotatingFile) SetInterval(interval int) *TimedRotatingFile {
	self.interval = int64(interval) * self.when
	self.reComputeRollover()
	return self
}

func (self TimedRotatingFile) shouldRollover() bool {
	if time.Now().Unix() >= self.rotatorAt {
		return true
	}
	return false
}

func (self *TimedRotatingFile) Close() (err error) {
	if err = self.w.Close(); err != nil {
		return
	}
	self.w = nil
	return
}

func (self *TimedRotatingFile) open() error {
	file, err := os.OpenFile(self.filename, FILE_MODE, FILE_PERM)
	if err != nil {
		return err
	} else {
		self.w = file
		return nil
	}
}

func (self *TimedRotatingFile) doRollover() (err error) {
	if err = self.Close(); err != nil {
		return
	}

	dstTime := self.rotatorAt - self.interval
	dstPath := self.filename + "." + time.Unix(dstTime, 0).Format(time2fmt[self.when])
	if file.IsExist(dstPath) {
		os.Remove(dstPath)
	}

	if file.IsFile(self.filename) {
		if err = os.Rename(self.filename, dstPath); err != nil {
			return err
		}
	}

	if self.backupCount > 0 {
		for _, file := range self.getFilesToDelete() {
			os.Remove(file)
		}
	}

	self.reComputeRollover()
	return self.open()
}

func (self TimedRotatingFile) getFilesToDelete() []string {
	result := make([]string, 0, 30)
	dirName, baseName := filepath.Split(self.filename)
	fileNames, err := file.ListDir(dirName)
	if err != nil {
		return result
	}

	var suffix, prefix string
	_prefix := baseName + "."
	plen := len(_prefix)
	for _, fileName := range fileNames {
		if len(fileName) <= plen {
			continue
		}
		prefix = string(fileName[:plen])
		if _prefix == prefix {
			suffix = string(fileName[plen:])
			if self.extRE.MatchString(suffix) {
				result = append(result, filepath.Join(dirName, fileName))
			}
		}
	}

	if len(result) <= self.backupCount {
		return []string{}
	} else {
		sort.Strings(result)
		return result[:len(result)-self.backupCount]
	}
}

func (self *TimedRotatingFile) reComputeRollover() {
	current_time := time.Now().Unix()

	t := time.Unix(current_time, 0)
	current_hour := t.Hour()
	current_minute := t.Minute()
	current_second := t.Second()

	r := self.interval - int64((current_hour*60+current_minute)*60+current_second)
	self.rotatorAt = current_time + r
}
<commit_msg>Update the ModePerm from 0777 to 0666<commit_after>// The handler of the logger.
package handler

import (
	"errors"
	"io"
	"os"
	"path/filepath"
	"regexp"
	"sort"
	"sync"
	"time"

	"github.com/xgfone/go-tools/file"
)

const (
	DAY_FMT = "2006-01-02"

	FILE_MODE             = os.O_APPEND | os.O_CREATE | os.O_WRONLY
	FILE_PERM os.FileMode = 0666
)

var (
	dayRE       = regexp.MustCompile(`^\d{4}-\d{2}-\d{2}(\.\w+)?$`)
	day   int64 = 3600 * 24

	time2fmt = map[int64]string{
		day: DAY_FMT,
	}
)

var (
	ErrFileNotOpen = errors.New("The file is not opened")
)

// A file handler based on the timed rotating, like
// `logging.handlers.TimedRotatingFileHandler` in Python.
// Now only support the rotation by day.
type TimedRotatingFile struct {
	sync.Mutex
	w io.WriteCloser

	filename    string
	backupCount int
	interval    int64
	when        int64
	rotatorAt   int64
	extRE       *regexp.Regexp
}

// Create a new TimedRotatingFile.
func NewTimedRotatingFile(filename string) *TimedRotatingFile {
	filename, _ = filepath.Abs(filename)
	t := TimedRotatingFile{filename: filename, when: day, extRE: dayRE}
	t.SetBackupCount(31).SetInterval(1)
	if err := t.open(); err != nil {
		panic(err)
	}
	return &t
}

// Write the string data into the file, which may rotate the file if necessary.
func (self *TimedRotatingFile) WriteString(data string) (n int, err error) {
	return self.Write([]byte(data))
}

// Write the byte slice data into the file, which may rotate the file if necessary.
func (self *TimedRotatingFile) Write(data []byte) (n int, err error) {
	self.Lock()
	defer self.Unlock()

	if self.w == nil {
		err = ErrFileNotOpen
		return
	}

	if self.shouldRollover() {
		if err = self.doRollover(); err != nil {
			return
		}
	}

	return self.w.Write(data)
}

// Set the number of the backup file. The default is 31.
func (self *TimedRotatingFile) SetBackupCount(num int) *TimedRotatingFile {
	self.backupCount = num
	return self
}

// Set the interval day number to rotate. The default is 1.
func (self *TimedRotatingFile) SetInterval(interval int) *TimedRotatingFile {
	self.interval = int64(interval) * self.when
	self.reComputeRollover()
	return self
}

func (self TimedRotatingFile) shouldRollover() bool {
	if time.Now().Unix() >= self.rotatorAt {
		return true
	}
	return false
}

func (self *TimedRotatingFile) Close() (err error) {
	if err = self.w.Close(); err != nil {
		return
	}
	self.w = nil
	return
}

func (self *TimedRotatingFile) open() error {
	file, err := os.OpenFile(self.filename, FILE_MODE, FILE_PERM)
	if err != nil {
		return err
	} else {
		self.w = file
		return nil
	}
}

func (self *TimedRotatingFile) doRollover() (err error) {
	if err = self.Close(); err != nil {
		return
	}

	dstTime := self.rotatorAt - self.interval
	dstPath := self.filename + "." + time.Unix(dstTime, 0).Format(time2fmt[self.when])
	if file.IsExist(dstPath) {
		os.Remove(dstPath)
	}

	if file.IsFile(self.filename) {
		if err = os.Rename(self.filename, dstPath); err != nil {
			return err
		}
	}

	if self.backupCount > 0 {
		for _, file := range self.getFilesToDelete() {
			os.Remove(file)
		}
	}

	self.reComputeRollover()
	return self.open()
}

func (self TimedRotatingFile) getFilesToDelete() []string {
	result := make([]string, 0, 30)
	dirName, baseName := filepath.Split(self.filename)
	fileNames, err := file.ListDir(dirName)
	if err != nil {
		return result
	}

	var suffix, prefix string
	_prefix := baseName + "."
	plen := len(_prefix)
	for _, fileName := range fileNames {
		if len(fileName) <= plen {
			continue
		}
		prefix = string(fileName[:plen])
		if _prefix == prefix {
			suffix = string(fileName[plen:])
			if self.extRE.MatchString(suffix) {
				result = append(result, filepath.Join(dirName, fileName))
			}
		}
	}

	if len(result) <= self.backupCount {
		return []string{}
	} else {
		sort.Strings(result)
		return result[:len(result)-self.backupCount]
	}
}

func (self *TimedRotatingFile) reComputeRollover() {
	current_time := time.Now().Unix()

	t := time.Unix(current_time, 0)
	current_hour := t.Hour()
	current_minute := t.Minute()
	current_second := t.Second()

	r := self.interval - int64((current_hour*60+current_minute)*60+current_second)
	self.rotatorAt = current_time + r
}
<|endoftext|>