<commit_before>package main

import (
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"testing"

	"github.com/fsouza/go-dockerclient"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
)

type TestDockerClient struct {
	containers []docker.APIContainers
	images     []docker.APIImages
	failures   map[string]error
}

func (rdc *TestDockerClient) ListContainers() ([]docker.APIContainers, error) {
	if err, ok := rdc.failures["ListContainers"]; ok {
		return []docker.APIContainers{}, err
	}
	return rdc.containers, nil
}

func (rdc *TestDockerClient) ListContainersWithLabels(labels []string) ([]docker.APIContainers, error) {
	if err, ok := rdc.failures["ListContainersWithLabels"]; ok {
		return []docker.APIContainers{}, err
	}
	return rdc.containers, nil
}

func (rdc *TestDockerClient) ListImages() ([]docker.APIImages, error) {
	if err, ok := rdc.failures["ListImages"]; ok {
		return []docker.APIImages{}, err
	}
	return rdc.images, nil
}

func (rdc *TestDockerClient) ListImagesWithLabels(labels []string) ([]docker.APIImages, error) {
	if err, ok := rdc.failures["ListImages"]; ok {
		return []docker.APIImages{}, err
	}
	return rdc.images, nil
}

func (rdc *TestDockerClient) ParseRepositoryTag(repoTag string) (string, string) {
	return docker.ParseRepositoryTag(repoTag)
}

func (rdc *TestDockerClient) PullImage(fullImage string, output *os.File) error {
	return nil
}

func (rdc *TestDockerClient) BuildImage(name string, dockerfile string, output io.Writer) error {
	return nil
}

func (rdc *TestDockerClient) CreateContainer(cco CreateContainerOpts) error {
	return nil
}

func (rdc *TestDockerClient) InspectContainer(cont string) (*docker.Container, error) {
	return nil, nil
}

func (rdc *TestDockerClient) StartContainer(name string) error {
	return nil
}

func (rdc *TestDockerClient) RemoveContainer(name string) error {
	return nil
}

func (rdc *TestDockerClient) StopContainer(name string) error {
	if err, ok := rdc.failures["StopContainer"]; ok {
		return err
	}
	var newContainers []docker.APIContainers
	for _, cont := range rdc.containers {
		if cont.Names[0] == fmt.Sprintf("/%s", name) {
			cont.Status = "Exited (0) 13 hours ago"
		}
		newContainers = append(newContainers, cont)
	}
	rdc.containers = newContainers

	return nil
}

func (rdc *TestDockerClient) AddContainer(container docker.APIContainers) error {
	rdc.containers = append(rdc.containers, container)
	return nil
}

func (rdc *TestDockerClient) AddImage(image docker.APIImages) error {
	rdc.images = append(rdc.images, image)
	return nil
}

func (rdc *TestDockerClient) AddFailure(name string, message error) {
	rdc.failures[name] = message
}

func (rdc *TestDockerClient) SetFailure(name string, message error) {
	rdc.ClearFailures()
	rdc.failures[name] = message
}

func (rdc *TestDockerClient) ClearFailures() {
	for k := range rdc.failures {
		delete(rdc.failures, k)
	}
}

type MockSystemClient struct {
	mock.Mock
}

func (rsc *MockSystemClient) DetectTimeZone() string {
	return "America/Los_Angeles"
}

func (msc *MockSystemClient) EnvironmentDirs() ([]string, error) {
	args := msc.Called()
	return args.Get(0).([]string), args.Error(1)
}

func (msc *MockSystemClient) Username() string {
	return "test"
}

func (msc *MockSystemClient) UID() int {
	return 1000
}

func (msc *MockSystemClient) GID() int {
	return 1000
}

func (msc *MockSystemClient) EnsureEnvironmentDir(envName string, keys SSHKey) (string, error) {
	return "", nil
}

func (msc *MockSystemClient) RemoveEnvironmentDir(envName string) error {
	return nil
}

func (msc *MockSystemClient) EnsureSSHKey() (SSHKey, error) {
	return SSHKey{}, nil
}

func NewTestDockerClient() (*TestDockerClient, error) {

	dockerClient := TestDockerClient{
		failures: make(map[string]error),
	}

	return &dockerClient, nil
}

func TestEnvironments(t *testing.T) {
	assert := assert.New(t)

	tempdir, _ := ioutil.TempDir("", "ddc")
	defer os.RemoveAll(tempdir)

	sc, _ := NewSystemClientWithBase(tempdir)

	dc, _ := NewTestDockerClient()
	dc.AddContainer(
		docker.APIContainers{
			ID:     "foo",
			Names:  []string{"/skeg_nate_foo"},
			Image:  "skeg-nate-1234",
			Status: "Up 12 hours",
			Ports: []docker.APIPort{
				{32768, 22, "tcp", "0.0.0.0"},
			},
			Labels: map[string]string{
				"skeg.io/image/base": "clojure",
			},
		},
	)
	key, _ := sc.EnsureSSHKey()
	sc.EnsureEnvironmentDir("foo", key)

	var envs map[string]Environment
	var err error

	envs, err = Environments(dc, sc)
	assert.Nil(err)
	assert.Equal(
		map[string]Environment{
			"foo": Environment{
				"foo",
				&Container{
					"skeg_nate_foo",
					"skeg-nate-1234",
					true,
					[]Port{{"0.0.0.0", 22, 32768, "tcp"}},
					map[string]string{
						"skeg.io/image/base": "clojure",
					},
				},
				"clojure",
			},
		},
		envs,
	)

	msc := new(MockSystemClient)
	dirError := errors.New("Dir listing error")
	msc.On("EnvironmentDirs").Return([]string{}, dirError)

	envs, err = Environments(dc, msc)
	assert.NotNil(err)
	assert.Equal(err, dirError)

	clError := errors.New("Container list error")
	dc.AddFailure("ListContainers", clError)

	envs, err = Environments(dc, sc)
	assert.NotNil(err)
	assert.Equal(err, clError)
}

func TestBaseImages(t *testing.T) {
	assert := assert.New(t)

	dc, _ := NewTestDockerClient()
	dc.AddImage(
		docker.APIImages{
			RepoTags: []string{
				"skegio/go:1.6",
			},
		},
	)
	dc.AddImage(
		docker.APIImages{
			RepoTags: []string{
				"skegio/python:3.5",
			},
		},
	)

	baseImages, err := BaseImages(dc)
	assert.Nil(err)

	assert.Equal(
		baseImages,
		[]*BaseImage{
			{
				"go",
				"Golang Image",
				[]*BaseImageTag{
					{"1.4", false, false},
					{"1.5", false, false},
					{"1.6", true, true},
				},
			},
			{
				"clojure",
				"Clojure image",
				[]*BaseImageTag{
					{"java7", false, false},
					{"java8", false, true},
				},
			},
			{
				"python",
				"Python base image",
				[]*BaseImageTag{
					{"both", false, true},
					{"2.7", false, false},
					{"3.5", true, false},
				},
			},
		},
	)
}

func TestEnsureImage(t *testing.T) {
	assert := assert.New(t)

	imageName := "dockdev/python:3.4"

	dc, _ := NewTestDockerClient()
	dc.AddImage(
		docker.APIImages{
			RepoTags: []string{
				imageName,
			},
		},
	)

	err := EnsureImage(dc, "testimage", false, nil)
	assert.Nil(err)

	err = EnsureImage(dc, imageName, false, nil)
	assert.Nil(err)

	liError := errors.New("Listing error")
	dc.AddFailure("ListImages", liError)

	err = EnsureImage(dc, imageName, false, nil)
	assert.NotNil(err)
	assert.Equal(err, liError)
}

func TestParsePorts(t *testing.T) {
	assert := assert.New(t)

	var portTests = []struct {
		input  []string
		output []Port
		err    error
	}{
		{[]string{}, []Port{}, nil},
		{[]string{"80"}, []Port{{"", 0, 80, "tcp"}}, nil},
		{[]string{"1194/udp"}, []Port{{"", 0, 1194, "udp"}}, nil},
		{[]string{"80:80"}, []Port{{"", 80, 80, "tcp"}}, nil},
		{[]string{"2222:22"}, []Port{}, errors.New("bad container port, 22 reserved for ssh")},
		{[]string{"7000-7005:7000"}, []Port{}, errors.New("dynamic port ranges not supported (yet)")},
		{[]string{"fred"}, []Port{}, errors.New("Invalid containerPort: fred")},
	}

	for _, test := range portTests {
		result, err := ParsePorts(test.input)
		assert.Equal(test.output, result)
		assert.Equal(test.err, err)
	}
}

func TestEnsureStopped(t *testing.T) {
	assert := assert.New(t)

	tempdir, _ := ioutil.TempDir("", "ddc")
	defer os.RemoveAll(tempdir)

	sc, _ := NewSystemClientWithBase(tempdir)

	dc, _ := NewTestDockerClient()
	dc.AddContainer(
		docker.APIContainers{
			ID:     "foo",
			Names:  []string{"/skeg_nate_foo"},
			Image:  "skeg-nate-1234",
			Status: "Up 12 hours",
			Ports: []docker.APIPort{
				{32768, 22, "tcp", "0.0.0.0"},
			},
			Labels: map[string]string{
				"skeg.io/image/base": "clojure",
			},
		},
	)
	key, _ := sc.EnsureSSHKey()
	sc.EnsureEnvironmentDir("foo", key)

	var env Environment
	var err error

	_, err = EnsureStopped(dc, sc, "bar")

	assert.Equal(err, errors.New("Environment bar doesn't exist."))

	liError := errors.New("Listing error")
	dc.SetFailure("ListContainers", liError)

	_, err = EnsureStopped(dc, sc, "foo")
	assert.Equal(err, liError)

	stopError := errors.New("Stop error")
	dc.SetFailure("StopContainer", stopError)

	_, err = EnsureStopped(dc, sc, "foo")
	assert.Equal(err, stopError)

	dc.ClearFailures()
	env, err = EnsureStopped(dc, sc, "foo")

	assert.False(env.Container.Running)
	assert.Nil(err)
}

// TODO: re-enable when TestDockerClient is a little smarter
// func TestCreateEnvironment(t *testing.T) {
// 	assert := assert.New(t)

// 	tempdir, _ := ioutil.TempDir("", "ddc")
// 	defer os.RemoveAll(tempdir)

// 	sc, _ := NewSystemClientWithBase(tempdir)

// 	dc, _ := NewTestDockerClient()

// 	co := CreateOpts{
// 		Name:       "foo",
// 		ProjectDir: "/tmp/foo",
// 		Ports:      []string{"3000"},
// 		Build: BuildOpts{
// 			Type:     "go",
// 			Version:  "1.6",
// 			Image:    "",
// 			Username: "user",
// 			UID:      1000,
// 			GID:      1000,
// 		},
// 	}

// 	var err error

// 	err = CreateEnvironment(dc, sc, co, bytes.NewBuffer(nil))
// 	assert.Nil(err)

// 	err = CreateEnvironment(dc, sc, co, bytes.NewBuffer(nil))
// 	assert.NotNil(err)
// 	assert.Regexp(regexp.MustCompile("already exists"), err)

// 	liError := errors.New("Listing error")
// 	dc.AddFailure("ListImages", liError)

// 	co.Name = "foo2"

// 	err = CreateEnvironment(dc, sc, co, bytes.NewBuffer(nil))
// 	assert.NotNil(err)
// 	assert.Equal(err, liError)

// }
<commit_msg>test EnsureRunning<commit_after>package main

import (
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"testing"

	"github.com/fsouza/go-dockerclient"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
)

type TestDockerClient struct {
	containers []docker.APIContainers
	images     []docker.APIImages
	failures   map[string]error
}

func (rdc *TestDockerClient) ListContainers() ([]docker.APIContainers, error) {
	if err, ok := rdc.failures["ListContainers"]; ok {
		return []docker.APIContainers{}, err
	}
	return rdc.containers, nil
}

func (rdc *TestDockerClient) ListContainersWithLabels(labels []string) ([]docker.APIContainers, error) {
	if err, ok := rdc.failures["ListContainersWithLabels"]; ok {
		return []docker.APIContainers{}, err
	}
	return rdc.containers, nil
}

func (rdc *TestDockerClient) ListImages() ([]docker.APIImages, error) {
	if err, ok := rdc.failures["ListImages"]; ok {
		return []docker.APIImages{}, err
	}
	return rdc.images, nil
}

func (rdc *TestDockerClient) ListImagesWithLabels(labels []string) ([]docker.APIImages, error) {
	if err, ok := rdc.failures["ListImages"]; ok {
		return []docker.APIImages{}, err
	}
	return rdc.images, nil
}

func (rdc *TestDockerClient) ParseRepositoryTag(repoTag string) (string, string) {
	return docker.ParseRepositoryTag(repoTag)
}

func (rdc *TestDockerClient) PullImage(fullImage string, output *os.File) error {
	return nil
}

func (rdc *TestDockerClient) BuildImage(name string, dockerfile string, output io.Writer) error {
	return nil
}

func (rdc *TestDockerClient) CreateContainer(cco CreateContainerOpts) error {
	return nil
}

func (rdc *TestDockerClient) InspectContainer(cont string) (*docker.Container, error) {
	return nil, nil
}

func (rdc *TestDockerClient) StartContainer(name string) error {
	if err, ok := rdc.failures["StartContainer"]; ok {
		return err
	}
	var newContainers []docker.APIContainers
	for _, cont := range rdc.containers {
		if cont.Names[0] == fmt.Sprintf("/%s", name) {
			cont.Status = "Up 12 hours"
		}
		newContainers = append(newContainers, cont)
	}
	rdc.containers = newContainers

	return nil
}

func (rdc *TestDockerClient) RemoveContainer(name string) error {
	return nil
}

func (rdc *TestDockerClient) StopContainer(name string) error {
	if err, ok := rdc.failures["StopContainer"]; ok {
		return err
	}
	var newContainers []docker.APIContainers
	for _, cont := range rdc.containers {
		if cont.Names[0] == fmt.Sprintf("/%s", name) {
			cont.Status = "Exited (0) 13 hours ago"
		}
		newContainers = append(newContainers, cont)
	}
	rdc.containers = newContainers

	return nil
}

func (rdc *TestDockerClient) AddContainer(container docker.APIContainers) error {
	rdc.containers = append(rdc.containers, container)
	return nil
}

func (rdc *TestDockerClient) AddImage(image docker.APIImages) error {
	rdc.images = append(rdc.images, image)
	return nil
}

func (rdc *TestDockerClient) AddFailure(name string, message error) {
	rdc.failures[name] = message
}

func (rdc *TestDockerClient) SetFailure(name string, message error) {
	rdc.ClearFailures()
	rdc.failures[name] = message
}

func (rdc *TestDockerClient) ClearFailures() {
	for k := range rdc.failures {
		delete(rdc.failures, k)
	}
}

type MockSystemClient struct {
	mock.Mock
}

func (rsc *MockSystemClient) DetectTimeZone() string {
	return "America/Los_Angeles"
}

func (msc *MockSystemClient) EnvironmentDirs() ([]string, error) {
	args := msc.Called()
	return args.Get(0).([]string), args.Error(1)
}

func (msc *MockSystemClient) Username() string {
	return "test"
}

func (msc *MockSystemClient) UID() int {
	return 1000
}

func (msc *MockSystemClient) GID() int {
	return 1000
}

func (msc *MockSystemClient) EnsureEnvironmentDir(envName string, keys SSHKey) (string, error) {
	return "", nil
}

func (msc *MockSystemClient) RemoveEnvironmentDir(envName string) error {
	return nil
}

func (msc *MockSystemClient) EnsureSSHKey() (SSHKey, error) {
	return SSHKey{}, nil
}

func NewTestDockerClient() (*TestDockerClient, error) {

	dockerClient := TestDockerClient{
		failures: make(map[string]error),
	}

	return &dockerClient, nil
}

func TestEnvironments(t *testing.T) {
	assert := assert.New(t)

	tempdir, _ := ioutil.TempDir("", "ddc")
	defer os.RemoveAll(tempdir)

	sc, _ := NewSystemClientWithBase(tempdir)

	dc, _ := NewTestDockerClient()
	dc.AddContainer(
		docker.APIContainers{
			ID:     "foo",
			Names:  []string{"/skeg_nate_foo"},
			Image:  "skeg-nate-1234",
			Status: "Up 12 hours",
			Ports: []docker.APIPort{
				{32768, 22, "tcp", "0.0.0.0"},
			},
			Labels: map[string]string{
				"skeg.io/image/base": "clojure",
			},
		},
	)
	key, _ := sc.EnsureSSHKey()
	sc.EnsureEnvironmentDir("foo", key)

	var envs map[string]Environment
	var err error

	envs, err = Environments(dc, sc)
	assert.Nil(err)
	assert.Equal(
		map[string]Environment{
			"foo": Environment{
				"foo",
				&Container{
					"skeg_nate_foo",
					"skeg-nate-1234",
					true,
					[]Port{{"0.0.0.0", 22, 32768, "tcp"}},
					map[string]string{
						"skeg.io/image/base": "clojure",
					},
				},
				"clojure",
			},
		},
		envs,
	)

	msc := new(MockSystemClient)
	dirError := errors.New("Dir listing error")
	msc.On("EnvironmentDirs").Return([]string{}, dirError)

	envs, err = Environments(dc, msc)
	assert.NotNil(err)
	assert.Equal(err, dirError)

	clError := errors.New("Container list error")
	dc.AddFailure("ListContainers", clError)

	envs, err = Environments(dc, sc)
	assert.NotNil(err)
	assert.Equal(err, clError)
}

func TestBaseImages(t *testing.T) {
	assert := assert.New(t)

	dc, _ := NewTestDockerClient()
	dc.AddImage(
		docker.APIImages{
			RepoTags: []string{
				"skegio/go:1.6",
			},
		},
	)
	dc.AddImage(
		docker.APIImages{
			RepoTags: []string{
				"skegio/python:3.5",
			},
		},
	)

	baseImages, err := BaseImages(dc)
	assert.Nil(err)

	assert.Equal(
		baseImages,
		[]*BaseImage{
			{
				"go",
				"Golang Image",
				[]*BaseImageTag{
					{"1.4", false, false},
					{"1.5", false, false},
					{"1.6", true, true},
				},
			},
			{
				"clojure",
				"Clojure image",
				[]*BaseImageTag{
					{"java7", false, false},
					{"java8", false, true},
				},
			},
			{
				"python",
				"Python base image",
				[]*BaseImageTag{
					{"both", false, true},
					{"2.7", false, false},
					{"3.5", true, false},
				},
			},
		},
	)
}

func TestEnsureImage(t *testing.T) {
	assert := assert.New(t)

	imageName := "dockdev/python:3.4"

	dc, _ := NewTestDockerClient()
	dc.AddImage(
		docker.APIImages{
			RepoTags: []string{
				imageName,
			},
		},
	)

	err := EnsureImage(dc, "testimage", false, nil)
	assert.Nil(err)

	err = EnsureImage(dc, imageName, false, nil)
	assert.Nil(err)

	liError := errors.New("Listing error")
	dc.AddFailure("ListImages", liError)

	err = EnsureImage(dc, imageName, false, nil)
	assert.NotNil(err)
	assert.Equal(err, liError)
}

func TestParsePorts(t *testing.T) {
	assert := assert.New(t)

	var portTests = []struct {
		input  []string
		output []Port
		err    error
	}{
		{[]string{}, []Port{}, nil},
		{[]string{"80"}, []Port{{"", 0, 80, "tcp"}}, nil},
		{[]string{"1194/udp"}, []Port{{"", 0, 1194, "udp"}}, nil},
		{[]string{"80:80"}, []Port{{"", 80, 80, "tcp"}}, nil},
		{[]string{"2222:22"}, []Port{}, errors.New("bad container port, 22 reserved for ssh")},
		{[]string{"7000-7005:7000"}, []Port{}, errors.New("dynamic port ranges not supported (yet)")},
		{[]string{"fred"}, []Port{}, errors.New("Invalid containerPort: fred")},
	}

	for _, test := range portTests {
		result, err := ParsePorts(test.input)
		assert.Equal(test.output, result)
		assert.Equal(test.err, err)
	}
}

func TestEnsureStopped(t *testing.T) {
	assert := assert.New(t)

	tempdir, _ := ioutil.TempDir("", "ddc")
	defer os.RemoveAll(tempdir)

	sc, _ := NewSystemClientWithBase(tempdir)

	dc, _ := NewTestDockerClient()
	dc.AddContainer(
		docker.APIContainers{
			ID:     "foo",
			Names:  []string{"/skeg_nate_foo"},
			Image:  "skeg-nate-1234",
			Status: "Up 12 hours",
			Ports: []docker.APIPort{
				{32768, 22, "tcp", "0.0.0.0"},
			},
			Labels: map[string]string{
				"skeg.io/image/base": "clojure",
			},
		},
	)
	key, _ := sc.EnsureSSHKey()
	sc.EnsureEnvironmentDir("foo", key)

	var env Environment
	var err error

	_, err = EnsureStopped(dc, sc, "bar")
	assert.Equal(err, errors.New("Environment bar doesn't exist."))

	liError := errors.New("Listing error")
	dc.SetFailure("ListContainers", liError)
	_, err = EnsureStopped(dc, sc, "foo")
	assert.Equal(err, liError)

	stopError := errors.New("Stop error")
	dc.SetFailure("StopContainer", stopError)
	_, err = EnsureStopped(dc, sc, "foo")
	assert.Equal(err, stopError)

	dc.ClearFailures()
	env, err = EnsureStopped(dc, sc, "foo")
	assert.False(env.Container.Running)
	assert.Nil(err)
}

func TestEnsureRunning(t *testing.T) {
	assert := assert.New(t)

	tempdir, _ := ioutil.TempDir("", "ddc")
	defer os.RemoveAll(tempdir)

	sc, _ := NewSystemClientWithBase(tempdir)

	dc, _ := NewTestDockerClient()
	dc.AddContainer(
		docker.APIContainers{
			ID:     "foo",
			Names:  []string{"/skeg_nate_foo"},
			Image:  "skeg-nate-1234",
			Status: "Exited (0) 1 hour ago",
			Ports: []docker.APIPort{
				{32768, 22, "tcp", "0.0.0.0"},
			},
			Labels: map[string]string{
				"skeg.io/image/base": "clojure",
			},
		},
	)
	key, _ := sc.EnsureSSHKey()
	sc.EnsureEnvironmentDir("foo", key)

	var env Environment
	var err error

	_, err = EnsureRunning(dc, sc, "bar")
	assert.Equal(err, errors.New("Environment bar doesn't exist."))

	liError := errors.New("Listing error")
	dc.SetFailure("ListContainers", liError)
	_, err = EnsureRunning(dc, sc, "foo")
	assert.Equal(err, liError)

	startError := errors.New("Start error")
	dc.SetFailure("StartContainer", startError)
	_, err = EnsureRunning(dc, sc, "foo")
	assert.Equal(err, startError)

	dc.ClearFailures()
	env, err = EnsureRunning(dc, sc, "foo")
	assert.True(env.Container.Running)
	assert.Nil(err)
}

// TODO: re-enable when TestDockerClient is a little smarter
// func TestCreateEnvironment(t *testing.T) {
// 	assert := assert.New(t)

// 	tempdir, _ := ioutil.TempDir("", "ddc")
// 	defer os.RemoveAll(tempdir)

// 	sc, _ := NewSystemClientWithBase(tempdir)

// 	dc, _ := NewTestDockerClient()

// 	co := CreateOpts{
// 		Name:       "foo",
// 		ProjectDir: "/tmp/foo",
// 		Ports:      []string{"3000"},
// 		Build: BuildOpts{
// 			Type:     "go",
// 			Version:  "1.6",
// 			Image:    "",
// 			Username: "user",
// 			UID:      1000,
// 			GID:      1000,
// 		},
// 	}

// 	var err error

// 	err = CreateEnvironment(dc, sc, co, bytes.NewBuffer(nil))
// 	assert.Nil(err)

// 	err = CreateEnvironment(dc, sc, co, bytes.NewBuffer(nil))
// 	assert.NotNil(err)
// 	assert.Regexp(regexp.MustCompile("already exists"), err)

// 	liError := errors.New("Listing error")
// 	dc.AddFailure("ListImages", liError)

// 	co.Name = "foo2"

// 	err = CreateEnvironment(dc, sc, co, bytes.NewBuffer(nil))
// 	assert.NotNil(err)
// 	assert.Equal(err, liError)

// }
<|endoftext|>


<commit_before>// Copyright 2016 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package apiserver

import (
	"io"
	"net/http"
	"net/url"
	"time"

	"github.com/google/go-querystring/query"
	"github.com/juju/errors"
	"github.com/juju/loggo"
	"github.com/juju/testing"
	jc "github.com/juju/testing/checkers"
	"golang.org/x/net/websocket"
	gc "gopkg.in/check.v1"
	"gopkg.in/juju/names.v2"

	"github.com/juju/juju/apiserver/params"
	"github.com/juju/juju/state"
	coretesting "github.com/juju/juju/testing"
	"github.com/juju/juju/version"
)

type LogStreamIntSuite struct {
	testing.IsolationSuite
}

var _ = gc.Suite(&LogStreamIntSuite{})

func (s *LogStreamIntSuite) TestParamConversion(c *gc.C) {
	cfg := params.LogStreamConfig{
		AllModels: true,
		Sink:      "spam",
	}
	req := s.newReq(c, cfg)

	stub := &testing.Stub{}
	source := &stubSource{stub: stub}
	source.ReturnGetStart = 10
	handler := logStreamEndpointHandler{
		stopCh:    nil,
		newSource: source.newSource,
	}

	reqHandler, err := handler.newLogStreamRequestHandler(req)
	c.Assert(err, jc.ErrorIsNil)

	c.Check(reqHandler.sendModelUUID, jc.IsTrue)
	stub.CheckCallNames(c, "newSource", "getStart", "newTailer")
	stub.CheckCall(c, 1, "getStart", "spam", true)
	stub.CheckCall(c, 2, "newTailer", &state.LogTailerParams{
		StartID:   10,
		AllModels: true,
	})
}

func (s *LogStreamIntSuite) TestFullRequest(c *gc.C) {
	cfg := params.LogStreamConfig{
		AllModels: true,
		Sink:      "eggs",
	}
	req := s.newReq(c, cfg)
	stub := &testing.Stub{}
	source := &stubSource{stub: stub}
	source.ReturnGetStart = 10
	logs := []state.LogRecord{{
		ID:        10,
		ModelUUID: "deadbeef-...",
		Version:   version.Current,
		Time:      time.Date(2015, 6, 19, 15, 34, 37, 0, time.UTC),
		Entity:    names.NewMachineTag("99"),
		Module:    "some.where",
		Location:  "code.go:42",
		Level:     loggo.INFO,
		Message:   "stuff happened",
	}, {
		ID:        20,
		ModelUUID: "deadbeef-...",
		Version:   version.Current,
		Time:      time.Date(2015, 6, 19, 15, 36, 40, 0, time.UTC),
		Entity:    names.NewUnitTag("foo/2"),
		Module:    "else.where",
		Location:  "go.go:22",
		Level:     loggo.ERROR,
		Message:   "whoops",
	}}
	var expected []params.LogStreamRecord
	for _, rec := range logs {
		expected = append(expected, params.LogStreamRecord{
			ID:        rec.ID,
			ModelUUID: rec.ModelUUID,
			Entity:    rec.Entity.String(),
			Version:   version.Current.String(),
			Timestamp: rec.Time,
			Module:    rec.Module,
			Location:  rec.Location,
			Level:     rec.Level.String(),
			Message:   rec.Message,
		})
	}
	tailer := &stubLogTailer{stub: stub}
	tailer.ReturnLogs = tailer.newChannel(logs)
	source.ReturnNewTailer = tailer
	reqHandler := &logStreamRequestHandler{
		req:           req,
		tailer:        tailer,
		sendModelUUID: true,
	}

	// Start the websocket server.
	stop := make(chan struct{})
	client := newWebsocketServer(c, func(conn *websocket.Conn) {
		defer conn.Close()
		stream, err := initStream(conn, nil)
		if err != nil {
			panic(err)
		}
		reqHandler.serveWebsocket(conn, stream, stop)
	})
	defer client.Close()
	defer close(stop)

	// Stream out the results from the client.
	okCh := make(chan params.ErrorResult)
	receivedCh := make(chan params.LogStreamRecord)
	go func() {
		var initial params.ErrorResult
		err := websocket.JSON.Receive(client, &initial)
		c.Assert(err, jc.ErrorIsNil)
		okCh <- initial

		for {
			var apiRec params.LogStreamRecord
			err := websocket.JSON.Receive(client, &apiRec)
			if err == io.EOF {
				break
			}
			c.Assert(err, jc.ErrorIsNil)
			receivedCh <- apiRec
		}
	}()

	// Check the OK message.
	select {
	case initial := <-okCh:
		c.Check(initial, jc.DeepEquals, params.ErrorResult{})
	case <-time.After(coretesting.LongWait):
		c.Fatal("timed out waiting for OK message")
	}

	// Check the records coming from the client.
	for i, expectedRec := range expected {
		c.Logf("trying #%d: %#v", i, expectedRec)
		select {
		case apiRec := <-receivedCh:
			c.Check(apiRec, jc.DeepEquals, expectedRec)
		case <-time.After(coretesting.LongWait):
			c.Fatal("timed out waiting for OK message")
		}
	}

	// Make sure there aren't any extras.
	select {
	case apiRec := <-receivedCh:
		c.Errorf("got extra: %#v", apiRec)
	default:
	}
}

func (s *LogStreamIntSuite) newReq(c *gc.C, cfg params.LogStreamConfig) *http.Request {
	attrs, err := query.Values(cfg)
	c.Assert(err, jc.ErrorIsNil)
	URL, err := url.Parse("https://a.b.c/logstream")
	c.Assert(err, jc.ErrorIsNil)
	URL.RawQuery = attrs.Encode()
	req, err := http.NewRequest("GET", URL.String(), nil)
	c.Assert(err, jc.ErrorIsNil)
	return req
}

type stubSource struct {
	stub *testing.Stub

	ReturnGetStart  int64
	ReturnNewTailer state.LogTailer
}

func (s *stubSource) newSource(req *http.Request) (logStreamSource, error) {
	s.stub.AddCall("newSource", req)
	if err := s.stub.NextErr(); err != nil {
		return nil, errors.Trace(err)
	}

	return s, nil
}

func (s *stubSource) getStart(sink string, allModels bool) (int64, error) {
	s.stub.AddCall("getStart", sink, allModels)
	if err := s.stub.NextErr(); err != nil {
		return 0, errors.Trace(err)
	}

	return s.ReturnGetStart, nil
}

func (s *stubSource) newTailer(args *state.LogTailerParams) (state.LogTailer, error) {
	s.stub.AddCall("newTailer", args)
	if err := s.stub.NextErr(); err != nil {
		return nil, errors.Trace(err)
	}

	return s.ReturnNewTailer, nil
}

type stubLogTailer struct {
	state.LogTailer
	stub *testing.Stub

	ReturnLogs <-chan *state.LogRecord
}

func (s *stubLogTailer) newChannel(logs []state.LogRecord) <-chan *state.LogRecord {
	ch := make(chan *state.LogRecord)
	go func() {
		for i := range logs {
			rec := logs[i]
			ch <- &rec
		}
	}()
	return ch
}

func (s *stubLogTailer) Logs() <-chan *state.LogRecord {
	s.stub.AddCall("Logs")
	s.stub.NextErr() // pop one off

	return s.ReturnLogs
}

func (s *stubLogTailer) Err() error {
	s.stub.AddCall("Err")
	if err := s.stub.NextErr(); err != nil {
		return errors.Trace(err)
	}

	return nil
}

func newWebsocketServer(c *gc.C, h websocket.Handler) *websocket.Conn {
	cfg, err := websocket.NewConfig("ws://localhost:12345/", "http://localhost/")
	c.Assert(err, jc.ErrorIsNil)

	go func() {
		err = http.ListenAndServe(":12345", websocket.Server{
			Config:  *cfg,
			Handler: h,
		})
		c.Assert(err, jc.ErrorIsNil)
	}()

	return newWebsocketClient(c, cfg)
}

func newWebsocketClient(c *gc.C, cfg *websocket.Config) *websocket.Conn {
	client, err := websocket.DialConfig(cfg)
	if err == nil {
		return client
	}

	timeoutCh := time.After(coretesting.LongWait)
	for {
		select {
		case <-timeoutCh:
			c.Assert(err, jc.ErrorIsNil)
		case <-time.After(coretesting.ShortWait):
		}

		client, err = websocket.DialConfig(cfg)
		if _, ok := err.(*websocket.DialError); ok {
			continue
		}
		c.Assert(err, jc.ErrorIsNil)
		return client
	}
}
<commit_msg>apiserver: fix test race lp:1596493<commit_after>// Copyright 2016 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package apiserver

import (
	"io"
	"net"
	"net/http"
	"net/url"
	"time"

	"github.com/google/go-querystring/query"
	"github.com/juju/errors"
	"github.com/juju/loggo"
	"github.com/juju/testing"
	jc "github.com/juju/testing/checkers"
	"golang.org/x/net/websocket"
	gc "gopkg.in/check.v1"
	"gopkg.in/juju/names.v2"

	"github.com/juju/juju/apiserver/params"
	"github.com/juju/juju/state"
	coretesting "github.com/juju/juju/testing"
	"github.com/juju/juju/version"
)

type LogStreamIntSuite struct {
	testing.IsolationSuite
}

var _ = gc.Suite(&LogStreamIntSuite{})

func (s *LogStreamIntSuite) TestParamConversion(c *gc.C) {
	cfg := params.LogStreamConfig{
		AllModels: true,
		Sink:      "spam",
	}
	req := s.newReq(c, cfg)

	stub := &testing.Stub{}
	source := &stubSource{stub: stub}
	source.ReturnGetStart = 10
	handler := logStreamEndpointHandler{
		stopCh:    nil,
		newSource: source.newSource,
	}

	reqHandler, err := handler.newLogStreamRequestHandler(req)
	c.Assert(err, jc.ErrorIsNil)

	c.Check(reqHandler.sendModelUUID, jc.IsTrue)
	stub.CheckCallNames(c, "newSource", "getStart", "newTailer")
	stub.CheckCall(c, 1, "getStart", "spam", true)
	stub.CheckCall(c, 2, "newTailer", &state.LogTailerParams{
		StartID:   10,
		AllModels: true,
	})
}

func (s *LogStreamIntSuite) TestFullRequest(c *gc.C) {

	// Create test data: i.e. log records for tailing...
	logs := []state.LogRecord{{
		ID:        10,
		ModelUUID: "deadbeef-...",
		Version:   version.Current,
		Time:      time.Date(2015, 6, 19, 15, 34, 37, 0, time.UTC),
		Entity:    names.NewMachineTag("99"),
		Module:    "some.where",
		Location:  "code.go:42",
		Level:     loggo.INFO,
		Message:   "stuff happened",
	}, {
		ID:        20,
		ModelUUID: "deadbeef-...",
		Version:   version.Current,
		Time:      time.Date(2015, 6, 19, 15, 36, 40, 0, time.UTC),
		Entity:    names.NewUnitTag("foo/2"),
		Module:    "else.where",
		Location:  "go.go:22",
		Level:     loggo.ERROR,
		Message:   "whoops",
	}}

	// ...and transform them into the records we expect to see.
	// (It would be better to create those records explicitly --
	// this is altogether too close to a violation of don't-copy-
	// the-implementation-into-the-tests.)
	var expected []params.LogStreamRecord
	for _, rec := range logs {
		expected = append(expected, params.LogStreamRecord{
			ID:        rec.ID,
			ModelUUID: rec.ModelUUID,
			Entity:    rec.Entity.String(),
			Version:   version.Current.String(),
			Timestamp: rec.Time,
			Module:    rec.Module,
			Location:  rec.Location,
			Level:     rec.Level.String(),
			Message:   rec.Message,
		})
	}

	// Create a tailer that will supply the source log records,
	// defined above, to the request handler we're (primarily)
	// testing, as set up in the next block; and create the
	// http request that the handler's execution is (purportedly)
	// caused by.
	tailer := &stubLogTailer{stub: &testing.Stub{}}
	tailer.ReturnLogs = tailer.newChannel(logs)
	req := s.newReq(c, params.LogStreamConfig{
		AllModels: true,
		Sink:      "eggs",
	})

	// Start the websocket server, which apes expected apiserver
	// behaviour by calling `initStream` and then handing over to
	// the `logStreamRequestHandler` as configured. That is to say:
	// this server callback holds everything that's *actually* being
	// tested here.
	serverDone := make(chan struct{})
	abortServer := make(chan struct{})
	client := newWebsocketServer(c, func(conn *websocket.Conn) {
		defer close(serverDone)
		defer conn.Close()

		stream, err := initStream(conn, nil)
		if !c.Check(err, jc.ErrorIsNil) {
			return
		}
		handler := &logStreamRequestHandler{
			req:           req,
			tailer:        tailer,
			sendModelUUID: true,
		}
		handler.serveWebsocket(conn, stream, abortServer)
	})
	defer waitFor(c, serverDone)
	defer close(abortServer)

	// Stream out the results from the client. This whole block is
	// just scaffolding to get the results back out on the records
	// channel, and should probably be replaced by something more
	// direct. (Does it *really* need its own goroutine?)
	clientDone := make(chan struct{})
	records := make(chan params.LogStreamRecord, 1000)
	go func() {
		defer close(clientDone)

		var result params.ErrorResult
		err := websocket.JSON.Receive(client, &result)
		ok := c.Check(err, jc.ErrorIsNil)
		if ok && c.Check(result, jc.DeepEquals, params.ErrorResult{}) {
			for {
				var apiRec params.LogStreamRecord
				err = websocket.JSON.Receive(client, &apiRec)
				if err != nil {
					break
				}
				records <- apiRec
			}
		}

		c.Logf("client stopped: %v", err)
		if err == io.EOF {
			return // this is fine
		}
		if _, ok := err.(*net.OpError); ok {
			return // so is this, probably
		}
		// anything else is a problem
		c.Check(err, jc.ErrorIsNil)
	}()
	defer waitFor(c, clientDone)
	defer client.Close()

	// Check the client produces the expected records. (This is the
	// actual *test* bit of the test, vs the scaffolding that
	// accounts for just about everything else.)
	for i, expectedRec := range expected {
		c.Logf("trying #%d: %#v", i, expectedRec)
		select {
		case apiRec := <-records:
			c.Check(apiRec, jc.DeepEquals, expectedRec)
		case <-time.After(coretesting.LongWait):
			c.Fatal("timed out waiting for log record")
		}
	}

	// Wait a moment to be sure there aren't any extra records.
	select {
	case apiRec := <-records:
		c.Errorf("got unexpected record: %#v", apiRec)
	case <-time.After(coretesting.ShortWait):
		// All good, let the defers handle teardown.
	}
}

func (s *LogStreamIntSuite) newReq(c *gc.C, cfg params.LogStreamConfig) *http.Request {
	attrs, err := query.Values(cfg)
	c.Assert(err, jc.ErrorIsNil)
	URL, err := url.Parse("https://a.b.c/logstream")
	c.Assert(err, jc.ErrorIsNil)
	URL.RawQuery = attrs.Encode()
	req, err := http.NewRequest("GET", URL.String(), nil)
	c.Assert(err, jc.ErrorIsNil)
	return req
}

type stubSource struct {
	stub *testing.Stub

	ReturnGetStart  int64
	ReturnNewTailer state.LogTailer
}

func (s *stubSource) newSource(req *http.Request) (logStreamSource, error) {
	s.stub.AddCall("newSource", req)
	if err := s.stub.NextErr(); err != nil {
		return nil, errors.Trace(err)
	}

	return s, nil
}

func (s *stubSource) getStart(sink string, allModels bool) (int64, error) {
	s.stub.AddCall("getStart", sink, allModels)
	if err := s.stub.NextErr(); err != nil {
		return 0, errors.Trace(err)
	}

	return s.ReturnGetStart, nil
}

func (s *stubSource) newTailer(args *state.LogTailerParams) (state.LogTailer, error) {
	s.stub.AddCall("newTailer", args)
	if err := s.stub.NextErr(); err != nil {
		return nil, errors.Trace(err)
	}

	return s.ReturnNewTailer, nil
}

type stubLogTailer struct {
	state.LogTailer
	stub *testing.Stub

	ReturnLogs <-chan *state.LogRecord
}

func (s *stubLogTailer) newChannel(logs []state.LogRecord) <-chan *state.LogRecord {
	ch := make(chan *state.LogRecord)
	go func() {
		for i := range logs {
			rec := logs[i]
			ch <- &rec
		}
	}()
	return ch
}

func (s *stubLogTailer) Logs() <-chan *state.LogRecord {
	s.stub.AddCall("Logs")
	s.stub.NextErr() // pop one off

	return s.ReturnLogs
}

func (s *stubLogTailer) Err() error {
	s.stub.AddCall("Err")
	if err := s.stub.NextErr(); err != nil {
		return errors.Trace(err)
	}

	return nil
}

func newWebsocketServer(c *gc.C, h websocket.Handler) *websocket.Conn {
	cfg, err := websocket.NewConfig("ws://localhost:12345/", "http://localhost/")
	c.Assert(err, jc.ErrorIsNil)

	go func() {
		err = http.ListenAndServe(":12345", websocket.Server{
			Config:  *cfg,
			Handler: h,
		})
		c.Assert(err, jc.ErrorIsNil)
	}()

	return newWebsocketClient(c, cfg)
}

func newWebsocketClient(c *gc.C, cfg *websocket.Config) *websocket.Conn {
	client, err := websocket.DialConfig(cfg)
	if err == nil {
		return client
	}

	timeoutCh := time.After(coretesting.LongWait)
	for {
		select {
		case <-timeoutCh:
			c.Assert(err, jc.ErrorIsNil)
		case <-time.After(coretesting.ShortWait):
		}

		client, err = websocket.DialConfig(cfg)
		if _, ok := err.(*websocket.DialError); ok {
			continue
		}
		c.Assert(err, jc.ErrorIsNil)
		return client
	}
}

func waitFor(c *gc.C, done <-chan struct{}) {
	select {
	case <-done:
	case <-time.After(coretesting.LongWait):
		c.Fatalf("channel never closed")
	}
}
<|endoftext|>


<commit_before>// Copyright 2011 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.

package hu

import (
	"reflect"
	"strings"
	"testing"
)

type lexTest struct {
	name  string
	input string
	items []item
}

var (
	tEOF   = item{itemEOF, ""}
	tQuote = item{itemString, `"abc \n\t\" "`}
)

var lexTests = []lexTest{
	{"empty", "", []item{tEOF}},
	{"words", "Red lentil soup",
		[]item{
			{itemWord, "Red"}, {itemSpace, " "},
			{itemWord, "lentil"}, {itemSpace, " "},
			{itemWord, "soup"},
			tEOF}},
	{"number and word", "1 onion",
		[]item{
			{itemNumber, "1"}, {itemSpace, " "},
			{itemWord, "onion"},
			tEOF}},
	{"colon", "Photo: apple",
		[]item{
			{itemWord, "Photo"}, {itemPunctuation, ":"}, {itemSpace, " "},
			{itemWord, "apple"},
			tEOF}},
	{"punctuation", "onion, chopped",
		[]item{
			{itemWord, "onion"}, {itemPunctuation, ","}, {itemSpace, " "},
			{itemWord, "chopped"},
			tEOF}},
}

// collect gathers the emitted items into a slice.
func collect(t *lexTest) (items []item) {
	l := lex(t.name, strings.NewReader(t.input))
	for {
		item := l.nextItem()
		items = append(items, item)
		if item.typ == itemEOF || item.typ == itemError {
			break
		}
	}
	return
}

func TestLex(t *testing.T) {
	for _, test := range lexTests {
		items := collect(&test)
		if !reflect.DeepEqual(items, test.items) {
			t.Errorf("%s: got\n\t%v\nexpected\n\t%v", test.name, items, test.items)
		}
	}
}
<commit_msg>fixed missed lex -> newReader edit<commit_after>// Copyright 2011 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.

package hu

import (
	"reflect"
	"strings"
	"testing"
)

type lexTest struct {
	name  string
	input string
	items []item
}

var (
	tEOF   = item{itemEOF, ""}
	tQuote = item{itemString, `"abc \n\t\" "`}
)

var lexTests = []lexTest{
	{"empty", "", []item{tEOF}},
	{"words", "Red lentil soup",
		[]item{
			{itemWord, "Red"}, {itemSpace, " "},
			{itemWord, "lentil"}, {itemSpace, " "},
			{itemWord, "soup"},
			tEOF}},
	{"number and word", "1 onion",
		[]item{
			{itemNumber, "1"}, {itemSpace, " "},
			{itemWord, "onion"},
			tEOF}},
	{"colon", "Photo: apple",
		[]item{
			{itemWord, "Photo"}, {itemPunctuation, ":"}, {itemSpace, " "},
			{itemWord, "apple"},
			tEOF}},
	{"punctuation", "onion, chopped",
		[]item{
			{itemWord, "onion"}, {itemPunctuation, ","}, {itemSpace, " "},
			{itemWord, "chopped"},
			tEOF}},
}

// collect gathers the emitted items into a slice.
func collect(t *lexTest) (items []item) {
	l := newReader(t.name, strings.NewReader(t.input))
	for {
		item := l.nextItem()
		items = append(items, item)
		if item.typ == itemEOF || item.typ == itemError {
			break
		}
	}
	return
}

func TestLex(t *testing.T) {
	for _, test := range lexTests {
		items := collect(&test)
		if !reflect.DeepEqual(items, test.items) {
			t.Errorf("%s: got\n\t%v\nexpected\n\t%v", test.name, items, test.items)
		}
	}
}
<|endoftext|>


<commit_before>package lfs

import (
	"bufio"
	"bytes"
	"crypto/tls"
	"errors"
	"fmt"
	"io"
	"net"
	"net/http"
	"net/http/httputil"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"github.com/github/git-lfs/vendor/_nuts/github.com/rubyist/tracerx"
)

type transferStats struct {
	HeaderSize int
	BodySize   int
	Start      time.Time
	Stop       time.Time
}

type transfer struct {
	requestStats  *transferStats
	responseStats *transferStats
	url           string
}

var (
	// TODO should use some locks
	transfers           = make(map[*http.Response]*transfer)
	transferBuckets     = make(map[string][]*http.Response)
	transfersLock       sync.Mutex
	transferBucketsLock sync.Mutex
)

func LogTransfer(key string, res *http.Response) {
	if Config.isLoggingStats {
		transferBucketsLock.Lock()
		transferBuckets[key] = append(transferBuckets[key], res)
		transferBucketsLock.Unlock()
	}
}

type HttpClient struct {
	*http.Client
}

func (c *HttpClient) Do(req *http.Request) (*http.Response, error) {
	traceHttpRequest(req)

	crc := countingRequest(req)
	if req.Body != nil {
		// Only set the body if we have a body, but create the countingRequest
		// anyway to make using zeroed stats easier.
		req.Body = crc
	}

	start := time.Now()
	res, err := c.Client.Do(req)
	if err != nil {
		return res, err
	}

	traceHttpResponse(res)

	cresp := countingResponse(res)
	res.Body = cresp

	if Config.isLoggingStats {
		reqHeaderSize := 0
		resHeaderSize := 0

		if dump, err := httputil.DumpRequest(req, false); err == nil {
			reqHeaderSize = len(dump)
		}

		if dump, err := httputil.DumpResponse(res, false); err == nil {
			resHeaderSize = len(dump)
		}

		reqstats := &transferStats{HeaderSize: reqHeaderSize, BodySize: crc.Count}

		// Response body size cannot be figured until it is read. Do not rely on a Content-Length
		// header because it may not exist or be -1 in the case of chunked responses.
		resstats := &transferStats{HeaderSize: resHeaderSize, Start: start}
		transfersLock.Lock()
		transfers[res] = &transfer{requestStats: reqstats, responseStats: resstats, url: req.URL.String()}
		transfersLock.Unlock()
	}

	return res, err
}

func DoHTTP(req *http.Request) (*http.Response, error) {
	res, err := Config.HttpClient().Do(req)
	if res == nil {
		res = &http.Response{StatusCode: 0, Header: make(http.Header), Request: req}
	}
	return res, err
}

func (c *Configuration) HttpClient() *HttpClient {
	if c.httpClient != nil {
		return c.httpClient
	}

	tr := &http.Transport{
		Proxy: http.ProxyFromEnvironment,
		Dial: (&net.Dialer{
			Timeout:   5 * time.Second,
			KeepAlive: 30 * time.Second,
		}).Dial,
		TLSHandshakeTimeout: 5 * time.Second,
	}

	sslVerify, _ := c.GitConfig("http.sslverify")
	if sslVerify == "false" || len(Config.Getenv("GIT_SSL_NO_VERIFY")) > 0 {
		tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
	}

	c.httpClient = &HttpClient{
		&http.Client{Transport: tr, CheckRedirect: checkRedirect},
	}

	return c.httpClient
}

func checkRedirect(req *http.Request, via []*http.Request) error {
	if len(via) >= 3 {
		return errors.New("stopped after 3 redirects")
	}

	oldest := via[0]
	for key, _ := range oldest.Header {
		if key == "Authorization" {
			if req.URL.Scheme != oldest.URL.Scheme || req.URL.Host != oldest.URL.Host {
				continue
			}
		}
		req.Header.Set(key, oldest.Header.Get(key))
	}

	tracerx.Printf("api: redirect %s %s to %s", oldest.Method, oldest.URL, req.URL)

	return nil
}

var tracedTypes = []string{"json", "text", "xml", "html"}

func traceHttpRequest(req *http.Request) {
	tracerx.Printf("HTTP: %s %s", req.Method, req.URL.String())

	if Config.isTracingHttp == false {
		return
	}

	dump, err := httputil.DumpRequest(req, false)
	if err != nil {
		return
	}

	scanner := bufio.NewScanner(bytes.NewBuffer(dump))
	for scanner.Scan() {
		fmt.Fprintf(os.Stderr, "> %s\n", scanner.Text())
	}
}

func traceHttpResponse(res *http.Response) {
	if res == nil {
		return
	}

	tracerx.Printf("HTTP: %d", res.StatusCode)

	if Config.isTracingHttp == false {
		return
	}

	dump, err := httputil.DumpResponse(res, false)
	if err != nil {
		return
	}

	scanner := bufio.NewScanner(bytes.NewBuffer(dump))
	for scanner.Scan() {
		fmt.Fprintf(os.Stderr, "< %s\n", scanner.Text())
	}
}

func countingRequest(req *http.Request) *countingReadCloser {
	return &countingReadCloser{request: req, ReadCloser: req.Body}
}

func countingResponse(res *http.Response) *countingReadCloser {
	return &countingReadCloser{response: res, ReadCloser: res.Body}
}

type countingReadCloser struct {
	Count    int
	request  *http.Request
	response *http.Response
	io.ReadCloser
}

func (c *countingReadCloser) Read(b []byte) (int, error) {
	n, err := c.ReadCloser.Read(b)
	if err != nil && err != io.EOF {
		return n, err
	}

	c.Count += n

	if Config.isTracingHttp {
		contentType := ""
		if c.response != nil { // Response, only print certain kinds of data
			contentType = strings.ToLower(strings.SplitN(c.response.Header.Get("Content-Type"), ";", 2)[0])
		} else {
			contentType = strings.ToLower(strings.SplitN(c.request.Header.Get("Content-Type"), ";", 2)[0])
		}

		for _, tracedType := range tracedTypes {
			if strings.Contains(contentType, tracedType) {
				fmt.Fprint(os.Stderr, string(b[0:n]))
			}
		}
	}

	if err == io.EOF && Config.isLoggingStats {
		// This transfer is done, we're checking it this way so we can also
		// catch transfers where the caller forgets to Close() the Body.
		if c.response != nil {
			transfersLock.Lock()
			if transfer, ok := transfers[c.response]; ok {
				transfer.responseStats.BodySize = c.Count
				transfer.responseStats.Stop = time.Now()
			}
			transfersLock.Unlock()
		}
	}
	return n, err
}

// LogHttpStats is intended to be called after all HTTP operations for the
// commmand have finished. It dumps k/v logs, one line per transfer into
// a log file with the current timestamp.
func LogHttpStats() {
	if !Config.isLoggingStats {
		return
	}

	file, err := statsLogFile()
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error logging http stats: %s\n", err)
		return
	}

	fmt.Fprintf(file, "concurrent=%d batch=%v time=%d version=%s\n", Config.ConcurrentTransfers(), Config.BatchTransfer(), time.Now().Unix(), Version)

	for key, responses := range transferBuckets {
		for _, response := range responses {
			stats := transfers[response]
			fmt.Fprintf(file, "key=%s reqheader=%d reqbody=%d resheader=%d resbody=%d restime=%d status=%d url=%s\n",
				key,
				stats.requestStats.HeaderSize,
				stats.requestStats.BodySize,
				stats.responseStats.HeaderSize,
				stats.responseStats.BodySize,
				stats.responseStats.Stop.Sub(stats.responseStats.Start).Nanoseconds(),
				response.StatusCode,
				stats.url)
		}
	}

	fmt.Fprintf(os.Stderr, "HTTP Stats logged to file %s\n", file.Name())
}

func statsLogFile() (*os.File, error) {
	logBase := filepath.Join(LocalLogDir, "http")
	if err := os.MkdirAll(logBase, 0755); err != nil {
		return nil, err
	}

	logFile := fmt.Sprintf("http-%d.log", time.Now().Unix())
	return os.Create(filepath.Join(logBase, logFile))
}
<commit_msg>アーア アアアア アーアー<commit_after>package lfs

import (
	"bufio"
	"bytes"
	"crypto/tls"
	"errors"
	"fmt"
	"io"
	"net"
	"net/http"
	"net/http/httputil"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"github.com/github/git-lfs/vendor/_nuts/github.com/rubyist/tracerx"
)

type transferStats struct {
	HeaderSize int
	BodySize   int
	Start      time.Time
	Stop       time.Time
}

type transfer struct {
	requestStats  *transferStats
	responseStats *transferStats
}

var (
	// TODO should use some locks
	transfers           = make(map[*http.Response]*transfer)
	transferBuckets     = make(map[string][]*http.Response)
	transfersLock       sync.Mutex
	transferBucketsLock sync.Mutex
)

func LogTransfer(key string, res *http.Response) {
	if Config.isLoggingStats {
		transferBucketsLock.Lock()
		transferBuckets[key] = append(transferBuckets[key], res)
		transferBucketsLock.Unlock()
	}
}

type HttpClient struct {
	*http.Client
}

func (c *HttpClient) Do(req *http.Request) (*http.Response, error) {
	traceHttpRequest(req)

	crc := countingRequest(req)
	if req.Body != nil {
		// Only set the body if we have a body, but create the countingRequest
		// anyway to make using zeroed stats easier.
		req.Body = crc
	}

	start := time.Now()
	res, err := c.Client.Do(req)
	if err != nil {
		return res, err
	}

	traceHttpResponse(res)

	cresp := countingResponse(res)
	res.Body = cresp

	if Config.isLoggingStats {
		reqHeaderSize := 0
		resHeaderSize := 0

		if dump, err := httputil.DumpRequest(req, false); err == nil {
			reqHeaderSize = len(dump)
		}

		if dump, err := httputil.DumpResponse(res, false); err == nil {
			resHeaderSize = len(dump)
		}

		reqstats := &transferStats{HeaderSize: reqHeaderSize, BodySize: crc.Count}

		// Response body size cannot be figured until it is read. Do not rely on a Content-Length
		// header because it may not exist or be -1 in the case of chunked responses.
		resstats := &transferStats{HeaderSize: resHeaderSize, Start: start}
		t := &transfer{requestStats: reqstats, responseStats: resstats}
		transfersLock.Lock()
		transfers[res] = t
		transfersLock.Unlock()
	}

	return res, err
}

func DoHTTP(req *http.Request) (*http.Response, error) {
	res, err := Config.HttpClient().Do(req)
	if res == nil {
		res = &http.Response{StatusCode: 0, Header: make(http.Header), Request: req}
	}
	return res, err
}

func (c *Configuration) HttpClient() *HttpClient {
	if c.httpClient != nil {
		return c.httpClient
	}

	tr := &http.Transport{
		Proxy: http.ProxyFromEnvironment,
		Dial: (&net.Dialer{
			Timeout:   5 * time.Second,
			KeepAlive: 30 * time.Second,
		}).Dial,
		TLSHandshakeTimeout: 5 * time.Second,
	}

	sslVerify, _ := c.GitConfig("http.sslverify")
	if sslVerify == "false" || len(Config.Getenv("GIT_SSL_NO_VERIFY")) > 0 {
		tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
	}

	c.httpClient = &HttpClient{
		&http.Client{Transport: tr, CheckRedirect: checkRedirect},
	}

	return c.httpClient
}

func checkRedirect(req *http.Request, via []*http.Request) error {
	if len(via) >= 3 {
		return errors.New("stopped after 3 redirects")
	}

	oldest := via[0]
	for key, _ := range oldest.Header {
		if key == "Authorization" {
			if req.URL.Scheme != oldest.URL.Scheme || req.URL.Host != oldest.URL.Host {
				continue
			}
		}
		req.Header.Set(key, oldest.Header.Get(key))
	}

	tracerx.Printf("api: redirect %s %s to %s", oldest.Method, oldest.URL, req.URL)

	return nil
}

var tracedTypes = []string{"json", "text", "xml", "html"}

func traceHttpRequest(req *http.Request) {
	tracerx.Printf("HTTP: %s %s", req.Method, req.URL.String())

	if Config.isTracingHttp == false {
		return
	}

	dump, err := httputil.DumpRequest(req, false)
	if err != nil {
		return
	}

	scanner := bufio.NewScanner(bytes.NewBuffer(dump))
	for scanner.Scan() {
		fmt.Fprintf(os.Stderr, "> %s\n", scanner.Text())
	}
}

func traceHttpResponse(res *http.Response) {
	if res == nil {
		return
	}

	tracerx.Printf("HTTP: %d", res.StatusCode)

	if Config.isTracingHttp == false {
		return
	}

	dump, err := httputil.DumpResponse(res, false)
	if err != nil {
		return
	}

	scanner := bufio.NewScanner(bytes.NewBuffer(dump))
	for scanner.Scan() {
		fmt.Fprintf(os.Stderr, "< %s\n", scanner.Text())
	}
}

func countingRequest(req *http.Request) *countingReadCloser {
	return &countingReadCloser{request: req, ReadCloser: req.Body}
}

func countingResponse(res *http.Response) *countingReadCloser {
	return &countingReadCloser{response: res, ReadCloser: res.Body}
}

type countingReadCloser struct {
	Count    int
	request  *http.Request
	response *http.Response
	io.ReadCloser
}

func (c *countingReadCloser) Read(b []byte) (int, error) {
	n, err := c.ReadCloser.Read(b)
	if err != nil && err != io.EOF {
		return n, err
	}

	c.Count += n

	if Config.isTracingHttp {
		contentType := ""
		if c.response != nil { // Response, only print certain kinds of data
			contentType = strings.ToLower(strings.SplitN(c.response.Header.Get("Content-Type"), ";", 2)[0])
		} else {
			contentType = strings.ToLower(strings.SplitN(c.request.Header.Get("Content-Type"), ";", 2)[0])
		}

		for _, tracedType := range tracedTypes {
			if strings.Contains(contentType, tracedType) {
				fmt.Fprint(os.Stderr, string(b[0:n]))
			}
		}
	}

	if err == io.EOF && Config.isLoggingStats {
		// This transfer is done, we're checking it this way so we can also
		// catch transfers where the caller forgets to Close() the Body.
		if c.response != nil {
			transfersLock.Lock()
			if transfer, ok := transfers[c.response]; ok {
				transfer.responseStats.BodySize = c.Count
				transfer.responseStats.Stop = time.Now()
			}
			transfersLock.Unlock()
		}
	}
	return n, err
}

// LogHttpStats is intended to be called after all HTTP operations for the
// commmand have finished. It dumps k/v logs, one line per transfer into
// a log file with the current timestamp.
func LogHttpStats() {
	if !Config.isLoggingStats {
		return
	}

	file, err := statsLogFile()
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error logging http stats: %s\n", err)
		return
	}

	fmt.Fprintf(file, "concurrent=%d batch=%v time=%d version=%s\n", Config.ConcurrentTransfers(), Config.BatchTransfer(), time.Now().Unix(), Version)

	for key, responses := range transferBuckets {
		for _, response := range responses {
			stats := transfers[response]
			fmt.Fprintf(file, "key=%s reqheader=%d reqbody=%d resheader=%d resbody=%d restime=%d status=%d url=%s\n",
				key,
				stats.requestStats.HeaderSize,
				stats.requestStats.BodySize,
				stats.responseStats.HeaderSize,
				stats.responseStats.BodySize,
				stats.responseStats.Stop.Sub(stats.responseStats.Start).Nanoseconds(),
				response.StatusCode,
				response.Request.URL)
		}
	}

	fmt.Fprintf(os.Stderr, "HTTP Stats logged to file %s\n", file.Name())
}

func statsLogFile() (*os.File, error) {
	logBase := filepath.Join(LocalLogDir, "http")
	if err := os.MkdirAll(logBase, 0755); err != nil {
		return nil, err
	}

	logFile := fmt.Sprintf("http-%d.log", time.Now().Unix())
	return os.Create(filepath.Join(logBase, logFile))
}
<|endoftext|>


<commit_before>package lfs

import (
	"fmt"
	"io"
	"os"
	"path/filepath"
	"runtime"
	"strings"
)

type CallbackReader struct {
	C         CopyCallback
	TotalSize int64
	ReadSize  int64
	io.Reader
}

type Platform int

const (
	PlatformWindows      = Platform(iota)
	PlatformLinux        = Platform(iota)
	PlatformOSX          = Platform(iota)
	PlatformOther        = Platform(iota) // most likely a *nix variant e.g. freebsd
	PlatformUndetermined = Platform(iota)
)

var currentPlatform = PlatformUndetermined

type CopyCallback func(totalSize int64, readSoFar int64, readSinceLast int) error

func (w *CallbackReader) Read(p []byte) (int, error) {
	n, err := w.Reader.Read(p)

	if n > 0 {
		w.ReadSize += int64(n)
	}

	if err == nil && w.C != nil {
		err = w.C(w.TotalSize, w.ReadSize, n)
	}

	return n, err
}

func CopyWithCallback(writer io.Writer, reader io.Reader, totalSize int64, cb CopyCallback) (int64, error) {
	if success, _ := CloneFile(writer, reader); success {
		if cb != nil {
			cb(totalSize, totalSize, 0)
		}
		return totalSize, nil
	}
	if cb == nil {
		return io.Copy(writer, reader)
	}

	cbReader := &CallbackReader{
		C:         cb,
		TotalSize: totalSize,
		Reader:    reader,
	}
	return io.Copy(writer, cbReader)
}

func CopyCallbackFile(event, filename string, index, totalFiles int) (CopyCallback, *os.File, error) {
	logPath := Config.Getenv("GIT_LFS_PROGRESS")
	if len(logPath) == 0 || len(filename) == 0 || len(event) == 0 {
		return nil, nil, nil
	}

	if !filepath.IsAbs(logPath) {
		return nil, nil, fmt.Errorf("GIT_LFS_PROGRESS must be an absolute path")
	}

	cbDir := filepath.Dir(logPath)
	if err := os.MkdirAll(cbDir, 0755); err != nil {
		return nil, nil, wrapProgressError(err, event, logPath)
	}

	file, err := os.OpenFile(logPath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
	if err != nil {
		return nil, file, wrapProgressError(err, event, logPath)
	}

	var prevWritten int64

	cb := CopyCallback(func(total int64, written int64, current int) error {
		if written != prevWritten {
			_, err := file.Write([]byte(fmt.Sprintf("%s %d/%d %d/%d %s\n", event, index, totalFiles, written, total, filename)))
			file.Sync()
			prevWritten = written
			return wrapProgressError(err, event, logPath)
		}

		return nil
	})

	return cb, file, nil
}

func wrapProgressError(err error, event, filename string) error {
	if err != nil {
		return fmt.Errorf("Error writing Git LFS %s progress to %s: %s", event, filename, err.Error())
	}

	return nil
}

var localDirSet = NewStringSetFromSlice([]string{".", "./", ".\\"})

// Return whether a given filename passes the include / exclude path filters
// Only paths that are in includePaths and outside excludePaths are passed
// If includePaths is empty that filter always passes and the same with excludePaths
// Both path lists support wildcard matches
func FilenamePassesIncludeExcludeFilter(filename string, includePaths, excludePaths []string) bool {
	if len(includePaths) == 0 && len(excludePaths) == 0 {
		return true
	}

	// For Win32, because git reports files with / separators
	cleanfilename := filepath.Clean(filename)
	if len(includePaths) > 0 {
		matched := false
		for _, inc := range includePaths {
			// Special case local dir, matches all (inc subpaths)
			if _, local := localDirSet[inc]; local {
				matched = true
				break
			}
			matched, _ = filepath.Match(inc, filename)
			if !matched && IsWindows() {
				// Also Win32 match
				matched, _ = filepath.Match(inc, cleanfilename)
			}
			if !matched {
				// Also support matching a parent directory without a wildcard
				if strings.HasPrefix(cleanfilename, inc+string(filepath.Separator)) {
					matched = true
				}
			}
			if matched {
				break
			}

		}
		if !matched {
			return false
		}
	}

	if len(excludePaths) > 0 {
		for _, ex := range excludePaths {
			// Special case local dir, matches all (inc subpaths)
			if _, local := localDirSet[ex]; local {
				return false
			}
			matched, _ := filepath.Match(ex, filename)
			if !matched && IsWindows() {
				// Also Win32 match
				matched, _ = filepath.Match(ex, cleanfilename)
			}
			if matched {
				return false
			}
			// Also support matching a parent directory without a wildcard
			if strings.HasPrefix(cleanfilename, ex+string(filepath.Separator)) {
				return false
			}

		}
	}

	return true
}

func GetPlatform() Platform {
	if currentPlatform == PlatformUndetermined {
		switch runtime.GOOS {
		case "windows":
			currentPlatform = PlatformWindows
		case "linux":
			currentPlatform = PlatformLinux
		case "darwin":
			currentPlatform = PlatformOSX
		default:
			currentPlatform = PlatformOther
		}
	}
	return currentPlatform
}

// Convert filenames expressed relative to the root of the repo relative to the
// current working dir. Useful when needing to calling git with results from a rooted command,
// but the user is in a subdir of their repo
// Pass in a channel which you will fill with relative files & receive a channel which will get results
func ConvertRepoFilesRelativeToCwd(repochan <-chan string) (<-chan string, error) {
	wd, err := os.Getwd()
	if err != nil {
		return nil, fmt.Errorf("Unable to get working dir: %v", err)
	}
	wd = ResolveSymlinks(wd)

	// Early-out if working dir is root dir, same result
	passthrough := false
	if LocalWorkingDir == wd {
		passthrough = true
	}

	outchan := make(chan string, 1)

	go func() {
		for f := range repochan {
			if passthrough {
				outchan <- f
				continue
			}
			abs := filepath.Join(LocalWorkingDir, f)
			rel, err := filepath.Rel(wd, abs)
			if err != nil {
				// Use absolute file instead
				outchan <- abs
			} else {
				outchan <- rel
			}
		}
		close(outchan)
	}()

	return outchan, nil
}

// Convert filenames expressed relative to the current directory to be
// relative to the repo root. Useful when calling git with arguments that requires them
// to be rooted but the user is in a subdir of their repo & expects to use relative args
// Pass in a channel which you will fill with relative files & receive a channel which will get results
func ConvertCwdFilesRelativeToRepo(cwdchan <-chan string) (<-chan string, error) {
	curdir, err := os.Getwd()
	if err != nil {
		return nil, fmt.Errorf("Could not retrieve current directory: %v", err)
	}
	// Make sure to resolve symlinks
	curdir = ResolveSymlinks(curdir)

	// Early-out if working dir is root dir, same result
	passthrough := false
	if LocalWorkingDir == curdir {
		passthrough = true
	}

	outchan := make(chan string, 1)
	go func() {
		for p := range cwdchan {
			if passthrough {
				outchan <- p
				continue
			}
			var abs string
			if filepath.IsAbs(p) {
				abs = ResolveSymlinks(p)
			} else {
				abs = filepath.Join(curdir, p)
			}
			reltoroot, err := filepath.Rel(LocalWorkingDir, abs)
			if err != nil {
				// Can't do this, use absolute as best fallback
				outchan <- abs
			} else {
				outchan <- reltoroot
			}
		}
		close(outchan)
	}()

	return outchan, nil

}

// ResolveSymlinks ensures that if the path supplied is a symlink, it is
// resolved to the actual concrete path
func ResolveSymlinks(path string) string {
	if resolved, err := filepath.EvalSymlinks(path); err == nil {
		return resolved
	}
	return path
}

// Are we running on Windows? Need to handle some extra path shenanigans
func IsWindows() bool {
	return GetPlatform() == PlatformWindows
}

// FileOrDirExists determines if a file/dir exists, returns IsDir() results too.
func FileOrDirExists(path string) (exists bool, isDir bool) {
	fi, err := os.Stat(path)
	if err != nil {
		return false, false
	} else {
		return true, fi.IsDir()
	}
}

// FileExists determines if a file (NOT dir) exists.
func FileExists(path string) bool {
	ret, isDir := FileOrDirExists(path)
	return ret && !isDir
}

// DirExists determines if a dir (NOT file) exists.
func DirExists(path string) bool {
	ret, isDir := FileOrDirExists(path)
	return ret && isDir
}

// FileExistsOfSize determines if a file exists and is of a specific size.
func FileExistsOfSize(path string, sz int64) bool {
	fi, err := os.Stat(path)

	if err != nil {
		return false
	}

	return !fi.IsDir() && fi.Size() == sz
}
<commit_msg>Skip blank paths in ResolveSymlinks<commit_after>package lfs

import (
	"fmt"
	"io"
	"os"
	"path/filepath"
	"runtime"
	"strings"
)

type CallbackReader struct {
	C         CopyCallback
	TotalSize int64
	ReadSize  int64
	io.Reader
}

type Platform int

const (
	PlatformWindows      = Platform(iota)
	PlatformLinux        = Platform(iota)
	PlatformOSX          = Platform(iota)
	PlatformOther        = Platform(iota) // most likely a *nix variant e.g. freebsd
	PlatformUndetermined = Platform(iota)
)

var currentPlatform = PlatformUndetermined

type CopyCallback func(totalSize int64, readSoFar int64, readSinceLast int) error

func (w *CallbackReader) Read(p []byte) (int, error) {
	n, err := w.Reader.Read(p)

	if n > 0 {
		w.ReadSize += int64(n)
	}

	if err == nil && w.C != nil {
		err = w.C(w.TotalSize, w.ReadSize, n)
	}

	return n, err
}

func CopyWithCallback(writer io.Writer, reader io.Reader, totalSize int64, cb CopyCallback) (int64, error) {
	if success, _ := CloneFile(writer, reader); success {
		if cb != nil {
			cb(totalSize, totalSize, 0)
		}
		return totalSize, nil
	}
	if cb == nil {
		return io.Copy(writer, reader)
	}

	cbReader := &CallbackReader{
		C:         cb,
		TotalSize: totalSize,
		Reader:    reader,
	}
	return io.Copy(writer, cbReader)
}

func CopyCallbackFile(event, filename string, index, totalFiles int) (CopyCallback, *os.File, error) {
	logPath := Config.Getenv("GIT_LFS_PROGRESS")
	if len(logPath) == 0 || len(filename) == 0 || len(event) == 0 {
		return nil, nil, nil
	}

	if !filepath.IsAbs(logPath) {
		return nil, nil, fmt.Errorf("GIT_LFS_PROGRESS must be an absolute path")
	}

	cbDir := filepath.Dir(logPath)
	if err := os.MkdirAll(cbDir, 0755); err != nil {
		return nil, nil, wrapProgressError(err, event, logPath)
	}

	file, err := os.OpenFile(logPath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
	if err != nil {
		return nil, file, wrapProgressError(err, event, logPath)
	}

	var prevWritten int64

	cb := CopyCallback(func(total int64, written int64, current int) error {
		if written != prevWritten {
			_, err := file.Write([]byte(fmt.Sprintf("%s %d/%d %d/%d %s\n", event, index, totalFiles, written, total, filename)))
			file.Sync()
			prevWritten = written
			return wrapProgressError(err, event, logPath)
		}

		return nil
	})

	return cb, file, nil
}

func wrapProgressError(err error, event, filename string) error {
	if err != nil {
		return fmt.Errorf("Error writing Git LFS %s progress to %s: %s", event, filename, err.Error())
	}

	return nil
}

var localDirSet = NewStringSetFromSlice([]string{".", "./", ".\\"})

// Return whether a given filename passes the include / exclude path filters
// Only paths that are in includePaths and outside excludePaths are passed
// If includePaths is empty that filter always passes and the same with excludePaths
// Both path lists support wildcard matches
func FilenamePassesIncludeExcludeFilter(filename string, includePaths, excludePaths []string) bool {
	if len(includePaths) == 0 && len(excludePaths) == 0 {
		return true
	}

	// For Win32, because git reports files with / separators
	cleanfilename := filepath.Clean(filename)
	if len(includePaths) > 0 {
		matched := false
		for _, inc := range includePaths {
			// Special case local dir, matches all (inc subpaths)
			if _, local := localDirSet[inc]; local {
				matched = true
				break
			}
			matched, _ = filepath.Match(inc, filename)
			if !matched && IsWindows() {
				// Also Win32 match
				matched, _ = filepath.Match(inc, cleanfilename)
			}
			if !matched {
				// Also support matching a parent directory without a wildcard
				if strings.HasPrefix(cleanfilename, inc+string(filepath.Separator)) {
					matched = true
				}
			}
			if matched {
				break
			}

		}
		if !matched {
			return false
		}
	}

	if len(excludePaths) > 0 {
		for _, ex := range excludePaths {
			// Special case local dir, matches all (inc subpaths)
			if _, local := localDirSet[ex]; local {
				return false
			}
			matched, _ := filepath.Match(ex, filename)
			if !matched && IsWindows() {
				// Also Win32 match
				matched, _ = filepath.Match(ex, cleanfilename)
			}
			if matched {
				return false
			}
			// Also support matching a parent directory without a wildcard
			if strings.HasPrefix(cleanfilename, ex+string(filepath.Separator)) {
				return false
			}

		}
	}

	return true
}

func GetPlatform() Platform {
	if currentPlatform == PlatformUndetermined {
		switch runtime.GOOS {
		case "windows":
			currentPlatform = PlatformWindows
		case "linux":
			currentPlatform = PlatformLinux
		case "darwin":
			currentPlatform = PlatformOSX
		default:
			currentPlatform = PlatformOther
		}
	}
	return currentPlatform
}

// Convert filenames expressed relative to the root of the repo relative to the
// current working dir. Useful when needing to calling git with results from a rooted command,
// but the user is in a subdir of their repo
// Pass in a channel which you will fill with relative files & receive a channel which will get results
func ConvertRepoFilesRelativeToCwd(repochan <-chan string) (<-chan string, error) {
	wd, err := os.Getwd()
	if err != nil {
		return nil, fmt.Errorf("Unable to get working dir: %v", err)
	}
	wd = ResolveSymlinks(wd)

	// Early-out if working dir is root dir, same result
	passthrough := false
	if LocalWorkingDir == wd {
		passthrough = true
	}

	outchan := make(chan string, 1)

	go func() {
		for f := range repochan {
			if passthrough {
				outchan <- f
				continue
			}
			abs := filepath.Join(LocalWorkingDir, f)
			rel, err := filepath.Rel(wd, abs)
			if err != nil {
				// Use absolute file instead
				outchan <- abs
			} else {
				outchan <- rel
			}
		}
		close(outchan)
	}()

	return outchan, nil
}

// Convert filenames expressed relative to the current directory to be
// relative to the repo root. Useful when calling git with arguments that requires them
// to be rooted but the user is in a subdir of their repo & expects to use relative args
// Pass in a channel which you will fill with relative files & receive a channel which will get results
func ConvertCwdFilesRelativeToRepo(cwdchan <-chan string) (<-chan string, error) {
	curdir, err := os.Getwd()
	if err != nil {
		return nil, fmt.Errorf("Could not retrieve current directory: %v", err)
	}
	// Make sure to resolve symlinks
	curdir = ResolveSymlinks(curdir)

	// Early-out if working dir is root dir, same result
	passthrough := false
	if LocalWorkingDir == curdir {
		passthrough = true
	}

	outchan := make(chan string, 1)
	go func() {
		for p := range cwdchan {
			if passthrough {
				outchan <- p
				continue
			}
			var abs string
			if filepath.IsAbs(p) {
				abs = ResolveSymlinks(p)
			} else {
				abs = filepath.Join(curdir, p)
			}
			reltoroot, err := filepath.Rel(LocalWorkingDir, abs)
			if err != nil {
				// Can't do this, use absolute as best fallback
				outchan <- abs
			} else {
				outchan <- reltoroot
			}
		}
		close(outchan)
	}()

	return outchan, nil

}

// ResolveSymlinks ensures that if the path supplied is a symlink, it is
// resolved to the actual concrete path
func ResolveSymlinks(path string) string {
	if len(path) == 0 {
		return path
	}

	if resolved, err := filepath.EvalSymlinks(path); err == nil {
		return resolved
	}
	return path
}

// Are we running on Windows? Need to handle some extra path shenanigans
func IsWindows() bool {
	return GetPlatform() == PlatformWindows
}

// FileOrDirExists determines if a file/dir exists, returns IsDir() results too.
func FileOrDirExists(path string) (exists bool, isDir bool) {
	fi, err := os.Stat(path)
	if err != nil {
		return false, false
	} else {
		return true, fi.IsDir()
	}
}

// FileExists determines if a file (NOT dir) exists.
func FileExists(path string) bool {
	ret, isDir := FileOrDirExists(path)
	return ret && !isDir
}

// DirExists determines if a dir (NOT file) exists.
func DirExists(path string) bool {
	ret, isDir := FileOrDirExists(path)
	return ret && isDir
}

// FileExistsOfSize determines if a file exists and is of a specific size.
func FileExistsOfSize(path string, sz int64) bool {
	fi, err := os.Stat(path)

	if err != nil {
		return false
	}

	return !fi.IsDir() && fi.Size() == sz
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"flag"
	"fmt"
	"io"
	"net/http"
	"os"
	"time"
)

const broadcastBuffSize = 1024 * 64  // 64Kb of data

func log(args ...interface{}) {
	t := time.Now()
	fmt.Printf("%d-%02d-%02dT%02d:%02d ",
		t.Year(), t.Month(), t.Day(),
		t.Hour(), t.Minute())
	fmt.Println(args...)
}

type MinimalHttpResponse struct {
	Body io.ReadCloser
}

type BroadcastSession interface {
	OpenUrl(url string) (*MinimalHttpResponse, error)
	StreamUrl() string
	Broadcast() chan []byte
	Quit() chan bool
	RetryCount() int
	MaxRetries() int
	IncrementRetry()
	ResetRetryCount()
}

type ChirpBroadcastSession struct {
	streamUrl string
	broadcast chan []byte
	maxRetries int
	quit chan bool
	retryCount int
	retrySleepTime time.Duration
}

func (sess *ChirpBroadcastSession) IncrementRetry() {
	time.Sleep(sess.retrySleepTime)
	sess.retryCount += 1
	log("Retrying...", sess.retryCount)
}

func (*ChirpBroadcastSession) OpenUrl(url string) (*MinimalHttpResponse, error) {
	response, err := http.Get(url)
	if err != nil {
		return &MinimalHttpResponse{}, err
	}
	return &MinimalHttpResponse{response.Body}, err
}

func (sess *ChirpBroadcastSession) StreamUrl() string {
	return sess.streamUrl
}

func (sess *ChirpBroadcastSession) Broadcast() chan []byte {
	return sess.broadcast
}

func (sess *ChirpBroadcastSession) Quit() chan bool {
	return sess.quit
}

func (sess *ChirpBroadcastSession) MaxRetries() int {
	return sess.maxRetries
}

func (sess *ChirpBroadcastSession) RetryCount() int {
	return sess.retryCount
}

func (sess *ChirpBroadcastSession) ResetRetryCount() {
	sess.retryCount = 0
}

func NewChirpBroadcastSession(
		streamUrl string, maxRetries int) BroadcastSession {
	broadcast := make(chan []byte, broadcastBuffSize)
	quit := make(chan bool)
	retryCount := 0
	retrySleepTime := 2 * time.Second

	return &ChirpBroadcastSession{
		streamUrl, broadcast, maxRetries, quit,
		retryCount, retrySleepTime}
}

func streamBroadcast(session BroadcastSession) error {

	if session.RetryCount() == session.MaxRetries() {
		log("streamBroadcast: too many error recovery retries")
		return errors.New("too many retries")
	}

	log("Streaming broadcast from", session.StreamUrl())
	response, err := session.OpenUrl(session.StreamUrl())

	if err != nil {
		log("Error while downloading", session.StreamUrl(), ":", err)
		session.IncrementRetry()
		return streamBroadcast(session)
	}
	defer response.Body.Close()

	for {
		buff := make([]byte, broadcastBuffSize)
		_, err := io.ReadFull(response.Body, buff)
		if err != nil {
			log("Error while streaming", session.StreamUrl(), ":", err)
			session.IncrementRetry()
			return streamBroadcast(session)
		}

		// We've successfully recovered from the last persistent error
		// so reset the retry count.
		if session.RetryCount() > 0 {
			log("Recovered from last error")
		}
		session.ResetRetryCount()

		select {
		case <-session.Quit():
			log("stopping stream from quit signal")
			return nil
		case session.Broadcast() <-buff:
			continue
		}
	}

	return nil
}

type ArchiveWriter interface {
	OpenFile() (io.WriteCloser, error)
	FileName() string
	Broadcast() chan []byte
	Quit() chan int
}

type ArchiveFileWriter struct {
	broadcast chan []byte
	quit chan int
	fileName string
}

func (w *ArchiveFileWriter) OpenFile() (io.WriteCloser, error) {
	log("Opening new archive file:", w.fileName)
	file, err := os.Create(w.fileName)
	return file, err
}

func (w *ArchiveFileWriter) FileName() string {
	return w.fileName
}

func (w *ArchiveFileWriter) Broadcast() chan []byte {
	return w.broadcast
}

func (w *ArchiveFileWriter) Quit() chan int {
	return w.quit
}

func NewArchiveFileWriter(
		broadcast chan []byte, quit chan int,
		fileName string) (*ArchiveFileWriter) {
	return &ArchiveFileWriter{broadcast, quit, fileName}
}

func writeArchiveFile(writer ArchiveWriter) error {
	output, err := writer.OpenFile()
	if err != nil {
		log("Error while creating", writer.FileName(), ":", err)
		return err
	}

	for {
		select {
		case streamChunk := <-writer.Broadcast():
			output.Write(streamChunk)
		case <-writer.Quit():
			output.Close()
			return nil
		}
	}
}

type ArchiveConfig interface {
	FileName(dest string, ts time.Time) string
	Dest(ts time.Time) string
	WriteFile(writer ArchiveWriter) error
}

type ChirpArchiveConfig struct {
	rootDir string
}

func (archive *ChirpArchiveConfig) Dest(ts time.Time) string {
	prefix := fmt.Sprintf("%s/%d/%02d", archive.rootDir, ts.Year(), ts.Month())
	err := os.MkdirAll(prefix, 0744)
	if err != nil {
		panic(err)
	}
	return prefix
}

func (*ChirpArchiveConfig) FileName(dest string, ts time.Time) string {
	// TODO: protect against overwriting existing files.
	return fmt.Sprintf(
		"%s/chirpradio_%d-%02d-%02d_%02d%02d%02d.mp3",
		dest,
		ts.Year(), ts.Month(), ts.Day(),
		ts.Hour(), ts.Minute(), ts.Second(),
	)
}

func (archive *ChirpArchiveConfig) WriteFile(writer ArchiveWriter) error {
	// TODO: maybe move the writeArchiveFile implementation over here :)
	return writeArchiveFile(writer)
}

func NewChirpArchiveConfig(rootDir string) (*ChirpArchiveConfig) {
	return &ChirpArchiveConfig{rootDir}
}

func rotateArchiveFile(
		broadcast chan []byte, ts time.Time, archive ArchiveConfig) chan int {
	dest := archive.Dest(ts)
	fileName := archive.FileName(dest, ts)
	archiveChan := make(chan int)

	writer := NewArchiveFileWriter(broadcast, archiveChan, fileName)
	go archive.WriteFile(writer)

	return archiveChan
}

func main() {
	var url string = "http://chirpradio.org/stream"
	flag.StringVar(
		&url, "url", url, "URL to the CHIRP Radio broadcast stream.")

	var archiveDest = "./archives"
	flag.StringVar(
		&archiveDest, "dest", archiveDest,
		"Directory to write archives to. This must exist and be writable.")

	flag.Parse()

	maxErrorRetries := 8
	session := NewChirpBroadcastSession(url, maxErrorRetries)
	broadcast := session.Broadcast()
	go streamBroadcast(session)

	archive := NewChirpArchiveConfig(archiveDest)
	archiveChan := rotateArchiveFile(broadcast, time.Now(), archive)

	// TODO: force Chicago time so files are always in sync with the broadcast.
	ticker := time.NewTicker(1 * time.Second)

	// Save the broadcast to disk, rotating the archive file at the start
	// of every hour.
	for {
		// The Go docs say that this might drop ticks for slow receivers.
		// TODO: address dropped ticks somehow?
		tick := <-ticker.C
		if tick.Minute() == 0 && tick.Second() == 0 {
			close(archiveChan)
			archiveChan = rotateArchiveFile(broadcast, tick, archive)
		}
	}
}
<commit_msg>Only reset retries when necessary<commit_after>package main

import (
	"errors"
	"flag"
	"fmt"
	"io"
	"net/http"
	"os"
	"time"
)

const broadcastBuffSize = 1024 * 64  // 64Kb of data

func log(args ...interface{}) {
	t := time.Now()
	fmt.Printf("%d-%02d-%02dT%02d:%02d ",
		t.Year(), t.Month(), t.Day(),
		t.Hour(), t.Minute())
	fmt.Println(args...)
}

type MinimalHttpResponse struct {
	Body io.ReadCloser
}

type BroadcastSession interface {
	OpenUrl(url string) (*MinimalHttpResponse, error)
	StreamUrl() string
	Broadcast() chan []byte
	Quit() chan bool
	RetryCount() int
	MaxRetries() int
	IncrementRetry()
	ResetRetryCount()
}

type ChirpBroadcastSession struct {
	streamUrl string
	broadcast chan []byte
	maxRetries int
	quit chan bool
	retryCount int
	retrySleepTime time.Duration
}

func (sess *ChirpBroadcastSession) IncrementRetry() {
	time.Sleep(sess.retrySleepTime)
	sess.retryCount += 1
	log("Retrying...", sess.retryCount)
}

func (*ChirpBroadcastSession) OpenUrl(url string) (*MinimalHttpResponse, error) {
	response, err := http.Get(url)
	if err != nil {
		return &MinimalHttpResponse{}, err
	}
	return &MinimalHttpResponse{response.Body}, err
}

func (sess *ChirpBroadcastSession) StreamUrl() string {
	return sess.streamUrl
}

func (sess *ChirpBroadcastSession) Broadcast() chan []byte {
	return sess.broadcast
}

func (sess *ChirpBroadcastSession) Quit() chan bool {
	return sess.quit
}

func (sess *ChirpBroadcastSession) MaxRetries() int {
	return sess.maxRetries
}

func (sess *ChirpBroadcastSession) RetryCount() int {
	return sess.retryCount
}

func (sess *ChirpBroadcastSession) ResetRetryCount() {
	sess.retryCount = 0
}

func NewChirpBroadcastSession(
		streamUrl string, maxRetries int) BroadcastSession {
	broadcast := make(chan []byte, broadcastBuffSize)
	quit := make(chan bool)
	retryCount := 0
	retrySleepTime := 2 * time.Second

	return &ChirpBroadcastSession{
		streamUrl, broadcast, maxRetries, quit,
		retryCount, retrySleepTime}
}

func streamBroadcast(session BroadcastSession) error {

	if session.RetryCount() == session.MaxRetries() {
		log("streamBroadcast: too many error recovery retries")
		return errors.New("too many retries")
	}

	log("Streaming broadcast from", session.StreamUrl())
	response, err := session.OpenUrl(session.StreamUrl())

	if err != nil {
		log("Error while downloading", session.StreamUrl(), ":", err)
		session.IncrementRetry()
		return streamBroadcast(session)
	}
	defer response.Body.Close()

	for {
		buff := make([]byte, broadcastBuffSize)
		_, err := io.ReadFull(response.Body, buff)
		if err != nil {
			log("Error while streaming", session.StreamUrl(), ":", err)
			session.IncrementRetry()
			return streamBroadcast(session)
		}

		if session.RetryCount() > 0 {
			log("Recovered from last error")
			session.ResetRetryCount()
		}

		select {
		case <-session.Quit():
			log("stopping stream from quit signal")
			return nil
		case session.Broadcast() <-buff:
			continue
		}
	}

	return nil
}

type ArchiveWriter interface {
	OpenFile() (io.WriteCloser, error)
	FileName() string
	Broadcast() chan []byte
	Quit() chan int
}

type ArchiveFileWriter struct {
	broadcast chan []byte
	quit chan int
	fileName string
}

func (w *ArchiveFileWriter) OpenFile() (io.WriteCloser, error) {
	log("Opening new archive file:", w.fileName)
	file, err := os.Create(w.fileName)
	return file, err
}

func (w *ArchiveFileWriter) FileName() string {
	return w.fileName
}

func (w *ArchiveFileWriter) Broadcast() chan []byte {
	return w.broadcast
}

func (w *ArchiveFileWriter) Quit() chan int {
	return w.quit
}

func NewArchiveFileWriter(
		broadcast chan []byte, quit chan int,
		fileName string) (*ArchiveFileWriter) {
	return &ArchiveFileWriter{broadcast, quit, fileName}
}

func writeArchiveFile(writer ArchiveWriter) error {
	output, err := writer.OpenFile()
	if err != nil {
		log("Error while creating", writer.FileName(), ":", err)
		return err
	}

	for {
		select {
		case streamChunk := <-writer.Broadcast():
			output.Write(streamChunk)
		case <-writer.Quit():
			output.Close()
			return nil
		}
	}
}

type ArchiveConfig interface {
	FileName(dest string, ts time.Time) string
	Dest(ts time.Time) string
	WriteFile(writer ArchiveWriter) error
}

type ChirpArchiveConfig struct {
	rootDir string
}

func (archive *ChirpArchiveConfig) Dest(ts time.Time) string {
	prefix := fmt.Sprintf("%s/%d/%02d", archive.rootDir, ts.Year(), ts.Month())
	err := os.MkdirAll(prefix, 0744)
	if err != nil {
		panic(err)
	}
	return prefix
}

func (*ChirpArchiveConfig) FileName(dest string, ts time.Time) string {
	// TODO: protect against overwriting existing files.
	return fmt.Sprintf(
		"%s/chirpradio_%d-%02d-%02d_%02d%02d%02d.mp3",
		dest,
		ts.Year(), ts.Month(), ts.Day(),
		ts.Hour(), ts.Minute(), ts.Second(),
	)
}

func (archive *ChirpArchiveConfig) WriteFile(writer ArchiveWriter) error {
	// TODO: maybe move the writeArchiveFile implementation over here :)
	return writeArchiveFile(writer)
}

func NewChirpArchiveConfig(rootDir string) (*ChirpArchiveConfig) {
	return &ChirpArchiveConfig{rootDir}
}

func rotateArchiveFile(
		broadcast chan []byte, ts time.Time, archive ArchiveConfig) chan int {
	dest := archive.Dest(ts)
	fileName := archive.FileName(dest, ts)
	archiveChan := make(chan int)

	writer := NewArchiveFileWriter(broadcast, archiveChan, fileName)
	go archive.WriteFile(writer)

	return archiveChan
}

func main() {
	var url string = "http://chirpradio.org/stream"
	flag.StringVar(
		&url, "url", url, "URL to the CHIRP Radio broadcast stream.")

	var archiveDest = "./archives"
	flag.StringVar(
		&archiveDest, "dest", archiveDest,
		"Directory to write archives to. This must exist and be writable.")

	flag.Parse()

	maxErrorRetries := 8
	session := NewChirpBroadcastSession(url, maxErrorRetries)
	broadcast := session.Broadcast()
	go streamBroadcast(session)

	archive := NewChirpArchiveConfig(archiveDest)
	archiveChan := rotateArchiveFile(broadcast, time.Now(), archive)

	// TODO: force Chicago time so files are always in sync with the broadcast.
	ticker := time.NewTicker(1 * time.Second)

	// Save the broadcast to disk, rotating the archive file at the start
	// of every hour.
	for {
		// The Go docs say that this might drop ticks for slow receivers.
		// TODO: address dropped ticks somehow?
		tick := <-ticker.C
		if tick.Minute() == 0 && tick.Second() == 0 {
			close(archiveChan)
			archiveChan = rotateArchiveFile(broadcast, tick, archive)
		}
	}
}
<|endoftext|>


<commit_before>package anime

import (
	"net/http"
	"sort"

	"github.com/aerogo/aero"
	"github.com/animenotifier/arn"
	"github.com/animenotifier/notify.moe/components"
	"github.com/animenotifier/notify.moe/utils"
)

const maxEpisodes = 26
const maxEpisodesLongSeries = 12
const maxDescriptionLength = 170

// Get anime page.
func Get(ctx *aero.Context) string {
	id := ctx.Get("id")
	user := utils.GetUser(ctx)
	anime, err := arn.GetAnime(id)

	if err != nil {
		return ctx.Error(http.StatusNotFound, "Anime not found", err)
	}

	episodes := anime.Episodes().Items

	if len(episodes) > maxEpisodes {
		episodes = anime.Episodes().LastReversed(maxEpisodesLongSeries)
	}

	// Friends watching
	var friends []*arn.User
	friendsAnimeListItems := map[*arn.User]*arn.AnimeListItem{}

	if user != nil {
		friends = user.Follows().Users()

		deleted := 0
		for i := range friends {
			j := i - deleted
			friendAnimeList := friends[j].AnimeList()
			friendAnimeListItem := friendAnimeList.Find(anime.ID)

			if friendAnimeListItem == nil || friendAnimeListItem.Private {
				friends = friends[:j+copy(friends[j:], friends[j+1:])]
				deleted++
			} else {
				friendsAnimeListItems[friends[j]] = friendAnimeListItem
			}
		}

		arn.SortUsersLastSeen(friends)
	}

	// Sort relations by start date
	relations := anime.Relations()

	if relations != nil {
		relations.SortByStartDate()
	}

	// Soundtracks
	tracks := arn.FilterSoundTracks(func(track *arn.SoundTrack) bool {
		return !track.IsDraft && len(track.Media) > 0 && arn.Contains(track.Tags, "anime:"+anime.ID)
	})

	sort.Slice(tracks, func(i, j int) bool {
		if len(tracks[i].Likes) == len(tracks[j].Likes) {
			return tracks[i].Title.ByUser(user) < tracks[j].Title.ByUser(user)
		}

		return len(tracks[i].Likes) > len(tracks[j].Likes)
	})

	// AMVs
	amvs := []*arn.AMV{}
	amvAppearances := []*arn.AMV{}

	for amv := range arn.StreamAMVs() {
		if amv.IsDraft {
			continue
		}

		if amv.MainAnimeID == anime.ID {
			amvs = append(amvs, amv)
		} else if arn.Contains(amv.ExtraAnimeIDs, anime.ID) {
			amvAppearances = append(amvAppearances, amv)
		}
	}

	sort.Slice(amvs, func(i, j int) bool {
		if len(amvs[i].Likes) == len(amvs[j].Likes) {
			return amvs[i].Title.ByUser(user) < amvs[j].Title.ByUser(user)
		}

		return len(amvs[i].Likes) > len(amvs[j].Likes)
	})

	// Anime list item
	var animeListItem *arn.AnimeListItem

	if user != nil {
		animeListItem = user.AnimeList().Find(anime.ID)
	}

	// Open Graph
	ctx.Data = getOpenGraph(ctx, anime)

	return ctx.HTML(components.Anime(anime, animeListItem, tracks, amvs, amvAppearances, episodes, friends, friendsAnimeListItems, user))
}

func getOpenGraph(ctx *aero.Context, anime *arn.Anime) *arn.OpenGraph {
	description := anime.Summary

	if len(description) > maxDescriptionLength {
		description = description[:maxDescriptionLength-3] + "..."
	}

	openGraph := &arn.OpenGraph{
		Tags: map[string]string{
			"og:title":       anime.Title.Canonical,
			"og:image":       "https:" + anime.ImageLink("large"),
			"og:url":         "https://" + ctx.App.Config.Domain + anime.Link(),
			"og:site_name":   "notify.moe",
			"og:description": description,
		},
		Meta: map[string]string{
			"description": description,
			"keywords":    anime.Title.Canonical + ",anime",
		},
	}

	switch anime.Type {
	case "tv":
		openGraph.Tags["og:type"] = "video.tv_show"
	case "movie":
		openGraph.Tags["og:type"] = "video.movie"
	}
}
<commit_msg>Fixed missing return<commit_after>package anime

import (
	"net/http"
	"sort"

	"github.com/aerogo/aero"
	"github.com/animenotifier/arn"
	"github.com/animenotifier/notify.moe/components"
	"github.com/animenotifier/notify.moe/utils"
)

const maxEpisodes = 26
const maxEpisodesLongSeries = 12
const maxDescriptionLength = 170

// Get anime page.
func Get(ctx *aero.Context) string {
	id := ctx.Get("id")
	user := utils.GetUser(ctx)
	anime, err := arn.GetAnime(id)

	if err != nil {
		return ctx.Error(http.StatusNotFound, "Anime not found", err)
	}

	episodes := anime.Episodes().Items

	if len(episodes) > maxEpisodes {
		episodes = anime.Episodes().LastReversed(maxEpisodesLongSeries)
	}

	// Friends watching
	var friends []*arn.User
	friendsAnimeListItems := map[*arn.User]*arn.AnimeListItem{}

	if user != nil {
		friends = user.Follows().Users()

		deleted := 0
		for i := range friends {
			j := i - deleted
			friendAnimeList := friends[j].AnimeList()
			friendAnimeListItem := friendAnimeList.Find(anime.ID)

			if friendAnimeListItem == nil || friendAnimeListItem.Private {
				friends = friends[:j+copy(friends[j:], friends[j+1:])]
				deleted++
			} else {
				friendsAnimeListItems[friends[j]] = friendAnimeListItem
			}
		}

		arn.SortUsersLastSeen(friends)
	}

	// Sort relations by start date
	relations := anime.Relations()

	if relations != nil {
		relations.SortByStartDate()
	}

	// Soundtracks
	tracks := arn.FilterSoundTracks(func(track *arn.SoundTrack) bool {
		return !track.IsDraft && len(track.Media) > 0 && arn.Contains(track.Tags, "anime:"+anime.ID)
	})

	sort.Slice(tracks, func(i, j int) bool {
		if len(tracks[i].Likes) == len(tracks[j].Likes) {
			return tracks[i].Title.ByUser(user) < tracks[j].Title.ByUser(user)
		}

		return len(tracks[i].Likes) > len(tracks[j].Likes)
	})

	// AMVs
	amvs := []*arn.AMV{}
	amvAppearances := []*arn.AMV{}

	for amv := range arn.StreamAMVs() {
		if amv.IsDraft {
			continue
		}

		if amv.MainAnimeID == anime.ID {
			amvs = append(amvs, amv)
		} else if arn.Contains(amv.ExtraAnimeIDs, anime.ID) {
			amvAppearances = append(amvAppearances, amv)
		}
	}

	sort.Slice(amvs, func(i, j int) bool {
		if len(amvs[i].Likes) == len(amvs[j].Likes) {
			return amvs[i].Title.ByUser(user) < amvs[j].Title.ByUser(user)
		}

		return len(amvs[i].Likes) > len(amvs[j].Likes)
	})

	// Anime list item
	var animeListItem *arn.AnimeListItem

	if user != nil {
		animeListItem = user.AnimeList().Find(anime.ID)
	}

	// Open Graph
	ctx.Data = getOpenGraph(ctx, anime)

	return ctx.HTML(components.Anime(anime, animeListItem, tracks, amvs, amvAppearances, episodes, friends, friendsAnimeListItems, user))
}

func getOpenGraph(ctx *aero.Context, anime *arn.Anime) *arn.OpenGraph {
	description := anime.Summary

	if len(description) > maxDescriptionLength {
		description = description[:maxDescriptionLength-3] + "..."
	}

	openGraph := &arn.OpenGraph{
		Tags: map[string]string{
			"og:title":       anime.Title.Canonical,
			"og:image":       "https:" + anime.ImageLink("large"),
			"og:url":         "https://" + ctx.App.Config.Domain + anime.Link(),
			"og:site_name":   "notify.moe",
			"og:description": description,
		},
		Meta: map[string]string{
			"description": description,
			"keywords":    anime.Title.Canonical + ",anime",
		},
	}

	switch anime.Type {
	case "tv":
		openGraph.Tags["og:type"] = "video.tv_show"
	case "movie":
		openGraph.Tags["og:type"] = "video.movie"
	}

	return openGraph
}
<|endoftext|>


<commit_before>package ast

import (
	"fmt"
	"log"
)

// StmtCopy performs a deep copy of passed stmt
func StmtCopy(in Stmt) (out Stmt) {

	switch v := in.(type) {
	case *AssignStmt:
		stmt := &AssignStmt{
			Lhs: ExprsCopy(v.Lhs),
			Rhs: ExprsCopy(v.Rhs),
		}
		out = stmt
	case *DeclStmt:
		stmt := &DeclStmt{}
		if v.Decl != nil {
			stmt.Decl = DeclCopy(v.Decl)
		}
		out = stmt
	case *BlockStmt:
		block := &BlockStmt{}
		list := make([]Stmt, 0, len(v.List))
		for _, stmt := range v.List {
			list = append(list, StmtCopy(stmt))
		}
		block.List = list
		out = block
	case *SelStmt:
		stmt := &SelStmt{
			Name:    IdentCopy(v.Name),
			NamePos: v.NamePos,
			Body:    StmtCopy(v.Body).(*BlockStmt),
			// SelDecl: DeclCopy(v.SelDecl).(*SelDecl),
			Sel: ExprCopy(v.Sel),
		}
		if v.Parent != nil {
			stmt.Parent = &SelStmt{
				Resolved: v.Parent.Resolved,
			}
		}

		names := make([]*Ident, 0, len(v.Names))
		for _, ident := range v.Names {
			names = append(names, IdentCopy(ident))
		}
		stmt.Names = names
		out = stmt
	case *CommStmt:
		out = v
		return
	case *IncludeStmt:
		stmt := &IncludeStmt{
			Spec: SpecCopy(v.Spec).(*IncludeSpec),
		}
		out = stmt
	case *EmptyStmt:
	default:
		log.Fatalf("unsupported stmt copy %T: % #v\n", v, v)
	}
	// fmt.Printf("StmtCopy (%p)% #v\n      ~> (%p)% #v\n", in, in, out, out)
	return
}

func ExprsCopy(in []Expr) []Expr {
	out := make([]Expr, 0, len(in))
	for i := range in {
		if in[i] != nil {
			out = append(out, ExprCopy(in[i]))
		}
	}
	return out
}

func ExprCopy(in Expr) (out Expr) {
	switch expr := in.(type) {
	case *Ident:
		out = IdentCopy(expr)
	case *BinaryExpr:
		out = &BinaryExpr{
			X:     ExprCopy(expr.X),
			Op:    expr.Op,
			OpPos: expr.OpPos,
			Y:     ExprCopy(expr.Y),
		}
	case *BasicLit:
		out = &BasicLit{
			Kind:     expr.Kind,
			Value:    expr.Value,
			ValuePos: expr.ValuePos,
		}
	case *KeyValueExpr:
		kv := &KeyValueExpr{}
		kv.Colon = expr.Colon
		kv.Key = ExprCopy(expr.Key)
		kv.Value = ExprCopy(expr.Value)
		out = kv
	default:
		log.Fatalf("unsupported expr copy: % #v\n", expr)
	}
	return
}

// IdentCopy does not resolve *Obj, this will need to
// be looked up after the fact
func IdentCopy(in *Ident) (out *Ident) {
	out = NewIdent(in.Name)
	return
	if in.Obj == nil {
		return
	}

	obj := NewObj(in.Obj.Kind, in.Obj.Name)
	// switch d := in.Obj.Decl.(type) {
	// case *AssignStmt:
	// 	out.Obj.Decl = StmtCopy(d)
	// case nil:
	// default:
	// 	log.Fatalf("unsupported obj: % #v\n", d)
	// }

	out.Obj = obj
	return
}

func FieldCopy(in *Field) (out *Field) {
	out = &Field{}
	out.Doc = in.Doc
	out.Names = make([]*Ident, len(in.Names))
	for i := range in.Names {
		out.Names[i] = IdentCopy(in.Names[i])
	}
	out.Type = ExprCopy(in.Type)
	out.Comment = in.Comment
	return
}

func FieldListCopy(in *FieldList) (out *FieldList) {
	out = &FieldList{}
	if in == nil || in.List == nil {
		return
	}
	list := make([]*Field, len(in.List))
	for i := range in.List {
		list[i] = FieldCopy(in.List[i])
	}
	out.List = list
	return
}

func SpecCopy(in Spec) (out Spec) {
	switch v := in.(type) {
	case *RuleSpec:
		spec := &RuleSpec{
			Name: NewIdent(v.Name.Name),
		}
		list := make([]Expr, 0, len(v.Values))
		for i := range v.Values {
			if v.Values[i] != nil {
				list = append(list, ExprCopy(v.Values[i]))
			}
		}
		spec.Values = list
		out = spec
	case *IncludeSpec:
		spec := &IncludeSpec{
			Name:   IdentCopy(v.Name),
			Params: FieldListCopy(v.Params),
		}
		list := make([]Stmt, len(v.List))
		for i := range v.List {
			list[i] = StmtCopy(v.List[i])
		}
		spec.List = list
		out = spec
	default:
		out = v
		log.Fatalf("unsupported spec copy %T: % #v\n", v, v)
		return
	}
	// fmt.Printf("SpecCopy % #v\n      ~> % #v\n", in, out)
	return
}

func DeclCopy(in Decl) (out Decl) {
	switch v := in.(type) {
	case *SelDecl:
		decl := &SelDecl{
			SelStmt: StmtCopy(v.SelStmt).(*SelStmt),
		}
		out = decl
	case *GenDecl:
		decl := *v
		list := make([]Spec, 0, len(decl.Specs))
		for i := range decl.Specs {
			if decl.Specs[i] != nil {
				list = append(list, SpecCopy(decl.Specs[i]))
			} else {
				fmt.Println("nil!")
			}
		}
		decl.Specs = list
		out = &decl
	default:
		log.Fatalf("unsupported decl copy %T: % #v\n", v, v)
	}
	return
}
<commit_msg>add unary to copy<commit_after>package ast

import (
	"fmt"
	"log"
)

// StmtCopy performs a deep copy of passed stmt
func StmtCopy(in Stmt) (out Stmt) {

	switch v := in.(type) {
	case *AssignStmt:
		stmt := &AssignStmt{
			Lhs: ExprsCopy(v.Lhs),
			Rhs: ExprsCopy(v.Rhs),
		}
		out = stmt
	case *DeclStmt:
		stmt := &DeclStmt{}
		if v.Decl != nil {
			stmt.Decl = DeclCopy(v.Decl)
		}
		out = stmt
	case *BlockStmt:
		block := &BlockStmt{}
		list := make([]Stmt, 0, len(v.List))
		for _, stmt := range v.List {
			list = append(list, StmtCopy(stmt))
		}
		block.List = list
		out = block
	case *SelStmt:
		stmt := &SelStmt{
			Name:    IdentCopy(v.Name),
			NamePos: v.NamePos,
			Body:    StmtCopy(v.Body).(*BlockStmt),
			// SelDecl: DeclCopy(v.SelDecl).(*SelDecl),
			Sel: ExprCopy(v.Sel),
		}
		if v.Parent != nil {
			stmt.Parent = &SelStmt{
				Resolved: v.Parent.Resolved,
			}
		}

		names := make([]*Ident, 0, len(v.Names))
		for _, ident := range v.Names {
			names = append(names, IdentCopy(ident))
		}
		stmt.Names = names
		out = stmt
	case *CommStmt:
		out = v
		return
	case *IncludeStmt:
		stmt := &IncludeStmt{
			Spec: SpecCopy(v.Spec).(*IncludeSpec),
		}
		out = stmt
	case *EmptyStmt:
	default:
		log.Fatalf("unsupported stmt copy %T: % #v\n", v, v)
	}
	// fmt.Printf("StmtCopy (%p)% #v\n      ~> (%p)% #v\n", in, in, out, out)
	return
}

func ExprsCopy(in []Expr) []Expr {
	out := make([]Expr, 0, len(in))
	for i := range in {
		if in[i] != nil {
			out = append(out, ExprCopy(in[i]))
		}
	}
	return out
}

func ExprCopy(in Expr) (out Expr) {
	switch expr := in.(type) {
	case *Ident:
		out = IdentCopy(expr)
	case *UnaryExpr:
		out = &UnaryExpr{
			Op:      expr.Op,
			OpPos:   expr.OpPos,
			X:       ExprCopy(expr.X),
			Visited: expr.Visited,
		}
	case *BinaryExpr:
		out = &BinaryExpr{
			X:     ExprCopy(expr.X),
			Op:    expr.Op,
			OpPos: expr.OpPos,
			Y:     ExprCopy(expr.Y),
		}
	case *BasicLit:
		out = &BasicLit{
			Kind:     expr.Kind,
			Value:    expr.Value,
			ValuePos: expr.ValuePos,
		}
	case *KeyValueExpr:
		kv := &KeyValueExpr{}
		kv.Colon = expr.Colon
		kv.Key = ExprCopy(expr.Key)
		kv.Value = ExprCopy(expr.Value)
		out = kv
	default:
		log.Fatalf("unsupported expr copy: % #v\n", expr)
	}
	return
}

// IdentCopy does not resolve *Obj, this will need to
// be looked up after the fact
func IdentCopy(in *Ident) (out *Ident) {
	out = NewIdent(in.Name)
	return
	if in.Obj == nil {
		return
	}

	obj := NewObj(in.Obj.Kind, in.Obj.Name)
	// switch d := in.Obj.Decl.(type) {
	// case *AssignStmt:
	// 	out.Obj.Decl = StmtCopy(d)
	// case nil:
	// default:
	// 	log.Fatalf("unsupported obj: % #v\n", d)
	// }

	out.Obj = obj
	return
}

func FieldCopy(in *Field) (out *Field) {
	out = &Field{}
	out.Doc = in.Doc
	out.Names = make([]*Ident, len(in.Names))
	for i := range in.Names {
		out.Names[i] = IdentCopy(in.Names[i])
	}
	out.Type = ExprCopy(in.Type)
	out.Comment = in.Comment
	return
}

func FieldListCopy(in *FieldList) (out *FieldList) {
	out = &FieldList{}
	if in == nil || in.List == nil {
		return
	}
	list := make([]*Field, len(in.List))
	for i := range in.List {
		list[i] = FieldCopy(in.List[i])
	}
	out.List = list
	return
}

func SpecCopy(in Spec) (out Spec) {
	switch v := in.(type) {
	case *RuleSpec:
		spec := &RuleSpec{
			Name: NewIdent(v.Name.Name),
		}
		list := make([]Expr, 0, len(v.Values))
		for i := range v.Values {
			if v.Values[i] != nil {
				list = append(list, ExprCopy(v.Values[i]))
			}
		}
		spec.Values = list
		out = spec
	case *IncludeSpec:
		spec := &IncludeSpec{
			Name:   IdentCopy(v.Name),
			Params: FieldListCopy(v.Params),
		}
		list := make([]Stmt, len(v.List))
		for i := range v.List {
			list[i] = StmtCopy(v.List[i])
		}
		spec.List = list
		out = spec
	default:
		out = v
		log.Fatalf("unsupported spec copy %T: % #v\n", v, v)
		return
	}
	// fmt.Printf("SpecCopy % #v\n      ~> % #v\n", in, out)
	return
}

func DeclCopy(in Decl) (out Decl) {
	switch v := in.(type) {
	case *SelDecl:
		decl := &SelDecl{
			SelStmt: StmtCopy(v.SelStmt).(*SelStmt),
		}
		out = decl
	case *GenDecl:
		decl := *v
		list := make([]Spec, 0, len(decl.Specs))
		for i := range decl.Specs {
			if decl.Specs[i] != nil {
				list = append(list, SpecCopy(decl.Specs[i]))
			} else {
				fmt.Println("nil!")
			}
		}
		decl.Specs = list
		out = &decl
	default:
		log.Fatalf("unsupported decl copy %T: % #v\n", v, v)
	}
	return
}
<|endoftext|>