<commit_before>package in_test

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"

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

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

	"github.com/idahobean/npm-resource"
	"github.com/idahobean/npm-resource/in"
)

var _ = Describe("In", func() {
	var (
		tmpDir  string
		cmd     *exec.Cmd
		request in.Request
	)

	loginArgs := []string{"-u", "abc", "-p", "def", "-e", "ghi@jkl.mno", "-r", "http://localhost:8080"}

	BeforeEach(func() {
		var err error

		tmpDir, err = ioutil.TempDir("", "npm_resource_in")
		Ω(err).ShouldNot(HaveOccurred())

		packagePath, err := filepath.Abs("../sample-node")
		Ω(err).ShouldNot(HaveOccurred())

		request = in.Request{
			Source: resource.Source{
				PackageName: "sample-node",
				Registry:    "http://localhost:8080",
			},
		}

		err = exec.Command("npm-cli-login", loginArgs...).Run()
		Ω(err).ShouldNot(HaveOccurred())

		args := []string{"publish", packagePath, "--registry", "http://localhost:8080", "--force"}
		err = exec.Command("npm", args...).Run()
		Ω(err).ShouldNot(HaveOccurred())

	})

	JustBeforeEach(func() {
		stdin := &bytes.Buffer{}

		err := json.NewEncoder(stdin).Encode(request)
		Ω(err).ShouldNot(HaveOccurred())

		cmd = exec.Command(binPath, tmpDir) // builded from test suite
		cmd.Stdin = stdin
		cmd.Dir = tmpDir
	})

	AfterEach(func() {
		err := os.RemoveAll(tmpDir)
		Ω(err).ShouldNot(HaveOccurred())

		args := []string{"unpublish", "sample-node", "--registry", "http://localhost:8080", "--force"}
		err = exec.Command("npm", args...).Run()
		Ω(err).ShouldNot(HaveOccurred())
	})

	Context("when command terminates correctly", func() {
		Context("packagename is fullfilled", func() {
			It("returns npm version", func() {
				session, err := gexec.Start(
					cmd,
					GinkgoWriter,
					GinkgoWriter,
				)
				Ω(err).ShouldNot(HaveOccurred())

				Eventually(session, "15s").Should(gexec.Exit(0))

				var response in.Response
				err = json.Unmarshal(session.Out.Contents(), &response)
				Ω(err).ShouldNot(HaveOccurred())

				Ω(response).Should(Equal(in.Response{
					Version: resource.Version{
						Version: "0.0.1",
					},
					Metadata: []resource.MetadataPair{
						{
							Name:  "name",
							Value: "sample-node",
						},
						{
							Name:  "homepage",
							Value: "https://github.com/idahobean/sample-node#readme"},
					},
				}))

				actual, err := exec.Command("npm", "ls", "sample-node")
				Ω(string(actual)).Should(ContainSubstring("sample-node@0.0.1"))
			})
		})
	})

	Context("when required option is empty", func() {
		Context("packagename is empty", func() {
			BeforeEach(func() {
				request.Source.PackageName = ""
			})

			It("returns an error", func() {
				session, err := gexec.Start(
					cmd,
					GinkgoWriter,
					GinkgoWriter,
				)
				Ω(err).ShouldNot(HaveOccurred())

				Eventually(session).Should(gexec.Exit(1))

				errMsg := fmt.Sprintf("error parameter required: package_name")
				Ω(session.Err).Should(gbytes.Say(errMsg))
			})
		})
	})
})
<commit_msg>fix integration test error<commit_after>package in_test

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"

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

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

	"github.com/idahobean/npm-resource"
	"github.com/idahobean/npm-resource/in"
)

var _ = Describe("In", func() {
	var (
		tmpDir  string
		cmd     *exec.Cmd
		request in.Request
	)

	loginArgs := []string{"-u", "abc", "-p", "def", "-e", "ghi@jkl.mno", "-r", "http://localhost:8080"}

	BeforeEach(func() {
		var err error

		tmpDir, err = ioutil.TempDir("", "npm_resource_in")
		Ω(err).ShouldNot(HaveOccurred())

		packagePath, err := filepath.Abs("../sample-node")
		Ω(err).ShouldNot(HaveOccurred())

		request = in.Request{
			Source: resource.Source{
				PackageName: "sample-node",
				Registry:    "http://localhost:8080",
			},
		}

		err = exec.Command("npm-cli-login", loginArgs...).Run()
		Ω(err).ShouldNot(HaveOccurred())

		args := []string{"publish", packagePath, "--registry", "http://localhost:8080", "--force"}
		err = exec.Command("npm", args...).Run()
		Ω(err).ShouldNot(HaveOccurred())

	})

	JustBeforeEach(func() {
		stdin := &bytes.Buffer{}

		err := json.NewEncoder(stdin).Encode(request)
		Ω(err).ShouldNot(HaveOccurred())

		cmd = exec.Command(binPath, tmpDir) // builded from test suite
		cmd.Stdin = stdin
		cmd.Dir = tmpDir
	})

	AfterEach(func() {
		err := os.RemoveAll(tmpDir)
		Ω(err).ShouldNot(HaveOccurred())

		args := []string{"unpublish", "sample-node", "--registry", "http://localhost:8080", "--force"}
		err = exec.Command("npm", args...).Run()
		Ω(err).ShouldNot(HaveOccurred())
	})

	Context("when command terminates correctly", func() {
		Context("packagename is fullfilled", func() {
			It("returns npm version", func() {
				session, err := gexec.Start(
					cmd,
					GinkgoWriter,
					GinkgoWriter,
				)
				Ω(err).ShouldNot(HaveOccurred())

				Eventually(session, "15s").Should(gexec.Exit(0))

				var response in.Response
				err = json.Unmarshal(session.Out.Contents(), &response)
				Ω(err).ShouldNot(HaveOccurred())

				Ω(response).Should(Equal(in.Response{
					Version: resource.Version{
						Version: "0.0.1",
					},
					Metadata: []resource.MetadataPair{
						{
							Name:  "name",
							Value: "sample-node",
						},
						{
							Name:  "homepage",
							Value: "https://github.com/idahobean/sample-node#readme"},
					},
				}))

				npmCmd := exec.Command("npm", "ls", "sample-node")
				npmCmd.Dir = tmpDir
				actual, err := npmCmd.Output()
				Ω(string(actual)).Should(ContainSubstring("sample-node@0.0.1"))
			})
		})
	})

	Context("when required option is empty", func() {
		Context("packagename is empty", func() {
			BeforeEach(func() {
				request.Source.PackageName = ""
			})

			It("returns an error", func() {
				session, err := gexec.Start(
					cmd,
					GinkgoWriter,
					GinkgoWriter,
				)
				Ω(err).ShouldNot(HaveOccurred())

				Eventually(session).Should(gexec.Exit(1))

				errMsg := fmt.Sprintf("error parameter required: package_name")
				Ω(session.Err).Should(gbytes.Say(errMsg))
			})
		})
	})
})
<|endoftext|>


<commit_before>// A Go random access reader for files in the dictzip format.
package dictzip

import (
	"compress/flate"
	"fmt"
	"io"
	"sync"
)

type Reader struct {
	fp        io.ReadSeeker
	offsets   []int64
	blocksize int64
	lock      sync.Mutex
}

func NewReader(fp io.ReadSeeker) (*Reader, error) {

	dz := &Reader{fp: fp}

	_, err := dz.fp.Seek(0, 0)
	if err != nil {
		return nil, err
	}

	metadata := []byte{}

	p := 0

	h := make([]byte, 10)
	n, err := readfull(dz.fp, h)
	if err != nil {
		return nil, err
	}
	p += n

	if h[0] != 31 || h[1] != 139 {
		return nil, fmt.Errorf("Invalid header: %02X %02X\n", h[0], h[1])
	}

	if h[2] != 8 {
		return nil, fmt.Errorf("Unknown compression method:", h[2])
	}

	flg := h[3]

	if flg&4 != 0 {
		h := make([]byte, 2)
		n, err := readfull(dz.fp, h)
		if err != nil {
			return nil, err
		}
		p += n

		xlen := int(h[0]) + 256*int(h[1])
		h = make([]byte, xlen)
		n, err = readfull(dz.fp, h)
		if err != nil {
			return nil, err
		}
		p += n

		for q := 0; q < len(h); {
			si1 := h[q]
			si2 := h[q+1]
			ln := int(h[q+2]) + 256*int(h[q+3])

			if si1 == 'R' && si2 == 'A' {
				metadata = h[q+4 : q+4+ln]
			}

			q += 4 + ln
		}

	}

	// skip file name (8), file comment (16)
	for _, f := range []byte{8, 16} {
		if flg&f != 0 {
			h := make([]byte, 1)
			for {
				n, err := readfull(dz.fp, h)
				if err != nil {
					return nil, err
				}
				p += n
				if h[0] == 0 {
					break
				}
			}
		}
	}

	if flg&2 != 0 {
		h := make([]byte, 2)
		n, err := readfull(dz.fp, h)
		if err != nil {
			return nil, err
		}
		p += n
	}

	if len(metadata) < 6 {
		return nil, fmt.Errorf("Missing dictzip metadata")
	}

	version := int(metadata[0]) + 256*int(metadata[1])

	if version != 1 {
		return nil, fmt.Errorf("Unknown dictzip version:", version)
	}

	dz.blocksize = int64(metadata[2]) + 256*int64(metadata[3])
	blockcnt := int(metadata[4]) + 256*int(metadata[5])

	dz.offsets = make([]int64, blockcnt+1)
	dz.offsets[0] = int64(p)
	for i := 0; i < blockcnt; i++ {
		dz.offsets[i+1] = dz.offsets[i] + int64(metadata[6+2*i]) + 256*int64(metadata[7+2*i])
	}

	return dz, nil

}

func (dz *Reader) Get(start, size int64) ([]byte, error) {

	dz.lock.Lock()
	defer dz.lock.Unlock()

	start1 := dz.blocksize * (start / dz.blocksize)
	size1 := size + (start - start1)

	_, err := dz.fp.Seek(dz.offsets[start/dz.blocksize], 0)
	if err != nil {
		return nil, err
	}
	rd := flate.NewReader(dz.fp)

	data := make([]byte, size1)
	_, err = readfull(rd, data)
	if err != nil {
		return nil, err
	}
	return data[start-start1:], nil
}

// Using start and size in base64 notation, such as used by the dictunzip program.
func (dz *Reader) GetB64(start, size string) ([]byte, error) {
	start2, err := decode(start)
	if err != nil {
		return nil, err
	}
	size2, err := decode(size)
	if err != nil {
		return nil, err
	}
	return dz.Get(start2, size2)
}

func readfull(fp io.Reader, buf []byte) (int, error) {
	ln := len(buf)
	for p := 0; p < ln; {
		n, err := fp.Read(buf[p:])
		p += n
		if err != nil {
			if err != io.EOF || p < ln {
				return p, err
			} else {
				return p, nil
			}
		}
	}
	return ln, nil
}

////////////////////////////////////////////////////////////////

var (
	list = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"

	index = []uint64{
		99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
		99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
		99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 62, 99, 99, 99, 63,
		52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 99, 99, 99, 99, 99, 99,
		99, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
		15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 99, 99, 99, 99, 99,
		99, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
		41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 99, 99, 99, 99, 99,
		99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
		99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
		99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
		99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
		99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
		99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
		99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
		99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
	}
)

func decode(val string) (int64, error) {
	var result uint64
	var offset uint64

	for i := len(val) - 1; i >= 0; i-- {
		tmp := index[val[i]]
		if tmp == 99 {
			return 0, fmt.Errorf("Illegal character in base64 value: %v", val[i:i+1])
		}

		if (tmp<<offset)>>offset != tmp {
			return 0, fmt.Errorf("Type uint64 cannot store decoded base64 value: %v", val)
		}

		result |= tmp << offset
		offset += 6
	}
	return int64(result), nil
}
<commit_msg>Added a Writer.<commit_after>/*
A Go reader and writer for files in the random access `dictzip` format.
*/
package dictzip

//. Imports

import (
	"bytes"
	"compress/flate"
	"fmt"
	"hash/crc32"
	"io"
	"os"
	"sync"
	"time"
)

//. Writer

/*
Levels range from 1 (BestSpeed) to 9 (BestCompression), Level 0 (NoCompression), -1 (DefaultCompression)
*/
func Write(r io.Reader, filename string, level int) error {

	const blocksize = 58315

	crc := crc32.NewIEEE()
	isize := 0

	var buf bytes.Buffer
	fw, err := flate.NewWriter(&buf, level)
	if err != nil {
		return err
	}
	sizes := make([]int, 0)
	b := make([]byte, blocksize)
	total := 0
	eof := false
	for !eof {
		n, err := readfull(r, b)
		if err != nil {
			if err != io.EOF {
				return err
			} else {
				eof = true
			}
		}
		if n > 0 {
			crc.Write(b[:n])
			isize += n

			fw.Write(b[:n])
			fw.Flush()
			fw.Reset(&buf)

			l := buf.Len()
			sizes = append(sizes, l-total)
			total = l
		}
	}
	fw.Close()

	fp, err := os.Create(filename)
	if err != nil {
		return err
	}
	defer fp.Close()

	xfl := byte(0)
	if level == flate.BestCompression {
		xfl = 2
	} else if level == flate.BestSpeed {
		xfl = 4
	}
	now := time.Now().Unix()
	_, err = fp.Write([]byte{
		31, 139, 8, 4,
		byte(now & 255), byte((now >> 8) & 255), byte((now >> 16) & 255), byte((now >> 24) & 255),
		xfl, 255})
	if err != nil {
		return err
	}

	xlen := 10 + 2*len(sizes)
	ln := 6 + 2*len(sizes)
	_, err = fp.Write([]byte{
		byte(xlen & 255), byte((xlen >> 8) & 255),
		'R', 'A', byte(ln & 255), byte((ln >> 8) & 255),
		1, 0,
		byte(blocksize & 255), byte((blocksize >> 8) & 255),
		byte(len(sizes) & 255), byte((len(sizes) >> 8) & 255)})
	if err != nil {
		return err
	}
	for _, o := range sizes {
		_, err = fp.Write([]byte{byte(o & 255), byte((o >> 8) & 255)})
		if err != nil {
			return err
		}
	}

	_, err = fp.Write(buf.Bytes())
	if err != nil {
		return err
	}

	c := crc.Sum32()
	_, err = fp.Write([]byte{
		byte(c & 255), byte((c >> 8) & 255), byte((c >> 16) & 255), byte((c >> 24) & 255),
		byte(isize & 255), byte((isize >> 8) & 255), byte((isize >> 16) & 255), byte((isize >> 24) & 255),
	})
	if err != nil {
		return err
	}

	return nil

}

//. Reader

type Reader struct {
	fp        io.ReadSeeker
	offsets   []int64
	blocksize int64
	lock      sync.Mutex
}

func NewReader(rs io.ReadSeeker) (*Reader, error) {

	dz := &Reader{fp: rs}

	_, err := dz.fp.Seek(0, 0)
	if err != nil {
		return nil, err
	}

	metadata := []byte{}

	p := 0

	h := make([]byte, 10)
	n, err := readfull(dz.fp, h)
	if err != nil {
		return nil, err
	}
	p += n

	if h[0] != 31 || h[1] != 139 {
		return nil, fmt.Errorf("Invalid header: %02X %02X\n", h[0], h[1])
	}

	if h[2] != 8 {
		return nil, fmt.Errorf("Unknown compression method:", h[2])
	}

	flg := h[3]

	if flg&4 != 0 {
		h := make([]byte, 2)
		n, err := readfull(dz.fp, h)
		if err != nil {
			return nil, err
		}
		p += n

		xlen := int(h[0]) + 256*int(h[1])
		h = make([]byte, xlen)
		n, err = readfull(dz.fp, h)
		if err != nil {
			return nil, err
		}
		p += n

		for q := 0; q < len(h); {
			si1 := h[q]
			si2 := h[q+1]
			ln := int(h[q+2]) + 256*int(h[q+3])

			if si1 == 'R' && si2 == 'A' {
				metadata = h[q+4 : q+4+ln]
			}

			q += 4 + ln
		}

	}

	// skip file name (8), file comment (16)
	for _, f := range []byte{8, 16} {
		if flg&f != 0 {
			h := make([]byte, 1)
			for {
				n, err := readfull(dz.fp, h)
				if err != nil {
					return nil, err
				}
				p += n
				if h[0] == 0 {
					break
				}
			}
		}
	}

	if flg&2 != 0 {
		h := make([]byte, 2)
		n, err := readfull(dz.fp, h)
		if err != nil {
			return nil, err
		}
		p += n
	}

	if len(metadata) < 6 {
		return nil, fmt.Errorf("Missing dictzip metadata")
	}

	version := int(metadata[0]) + 256*int(metadata[1])

	if version != 1 {
		return nil, fmt.Errorf("Unknown dictzip version:", version)
	}

	dz.blocksize = int64(metadata[2]) + 256*int64(metadata[3])
	blockcnt := int(metadata[4]) + 256*int(metadata[5])

	dz.offsets = make([]int64, blockcnt+1)
	dz.offsets[0] = int64(p)
	for i := 0; i < blockcnt; i++ {
		dz.offsets[i+1] = dz.offsets[i] + int64(metadata[6+2*i]) + 256*int64(metadata[7+2*i])
	}

	return dz, nil

}

func (dz *Reader) Get(start, size int64) ([]byte, error) {

	dz.lock.Lock()
	defer dz.lock.Unlock()

	start1 := dz.blocksize * (start / dz.blocksize)
	size1 := size + (start - start1)

	_, err := dz.fp.Seek(dz.offsets[start/dz.blocksize], 0)
	if err != nil {
		return nil, err
	}
	rd := flate.NewReader(dz.fp)

	data := make([]byte, size1)
	_, err = readfull(rd, data)
	if err != nil {
		return nil, err
	}

	return data[start-start1:], nil
}

// Using start and size in base64 notation, such as used by the dictunzip program.
func (dz *Reader) GetB64(start, size string) ([]byte, error) {
	start2, err := decode(start)
	if err != nil {
		return nil, err
	}
	size2, err := decode(size)
	if err != nil {
		return nil, err
	}
	return dz.Get(start2, size2)
}

//. Helper function

func readfull(fp io.Reader, buf []byte) (int, error) {
	ln := len(buf)
	for p := 0; p < ln; {
		n, err := fp.Read(buf[p:])
		p += n
		if err != nil {
			if err != io.EOF || p < ln {
				return p, err
			} else {
				return p, nil
			}
		}
	}
	return ln, nil
}

//. Base64 decoder

var (
	list = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"

	index = []uint64{
		99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
		99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
		99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 62, 99, 99, 99, 63,
		52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 99, 99, 99, 99, 99, 99,
		99, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
		15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 99, 99, 99, 99, 99,
		99, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
		41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 99, 99, 99, 99, 99,
		99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
		99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
		99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
		99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
		99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
		99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
		99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
		99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
	}
)

func decode(val string) (int64, error) {
	var result uint64
	var offset uint64

	for i := len(val) - 1; i >= 0; i-- {
		tmp := index[val[i]]
		if tmp == 99 {
			return 0, fmt.Errorf("Illegal character in base64 value: %v", val[i:i+1])
		}

		if (tmp<<offset)>>offset != tmp {
			return 0, fmt.Errorf("Type uint64 cannot store decoded base64 value: %v", val)
		}

		result |= tmp << offset
		offset += 6
	}
	return int64(result), nil
}
<|endoftext|>


<commit_before>package web_test

import (
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
)

func TestURLRedirect(t *testing.T) {
	request, _ := http.NewRequest("GET", "/url", nil)
	response := httptest.NewRecorder()

	router.ServeHTTP(response, request)

	if response.Code != http.StatusMovedPermanently {
		t.Errorf("Expected HTTP 301 Moved Permanently, but got HTTP %d instead", response.Code)
	}

	header := response.Header().Get("Location")
	expectedURL := "https://ariejan.net"
	if header != expectedURL {
		t.Errorf("Expected redirect to 'https://ariejan.net', got '%s' instead.", header)
	}

	body := response.Body.String()
	expectedTag := "Moved Permanently"
	if !strings.Contains(body, expectedTag) {
		t.Errorf("Expected redirect body to contain '%s', but it did not.", expectedTag)
	}
}
<commit_msg>Handle not found items<commit_after>package web_test

import (
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
)

func TestItemNotFound(t *testing.T) {
	request, _ := http.NewRequest("GET", "/nope", nil)
	response := httptest.NewRecorder()

	router.ServeHTTP(response, request)

	if response.Code != http.StatusNotFound {
		t.Errorf("Expected HTTP 404 Not Found, but got HTTP %d instead", response.Code)
	}
}

func TestURLRedirect(t *testing.T) {
	request, _ := http.NewRequest("GET", "/url", nil)
	response := httptest.NewRecorder()

	router.ServeHTTP(response, request)

	if response.Code != http.StatusMovedPermanently {
		t.Errorf("Expected HTTP 301 Moved Permanently, but got HTTP %d instead", response.Code)
	}

	header := response.Header().Get("Location")
	expectedURL := "https://ariejan.net"
	if header != expectedURL {
		t.Errorf("Expected redirect to 'https://ariejan.net', got '%s' instead.", header)
	}

	body := response.Body.String()
	expectedTag := "Moved Permanently"
	if !strings.Contains(body, expectedTag) {
		t.Errorf("Expected redirect body to contain '%s', but it did not.", expectedTag)
	}
}
<|endoftext|>


<commit_before>// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.

package httpd // import "miniflux.app/service/httpd"

import (
	"crypto/tls"
	"net"
	"net/http"
	"os"
	"strings"
	"time"

	"miniflux.app/api"
	"miniflux.app/config"
	"miniflux.app/fever"
	"miniflux.app/logger"
	"miniflux.app/reader/feed"
	"miniflux.app/storage"
	"miniflux.app/ui"
	"miniflux.app/worker"

	"github.com/gorilla/mux"
	"golang.org/x/crypto/acme/autocert"
)

// Serve starts a new HTTP server.
func Serve(cfg *config.Config, store *storage.Storage, pool *worker.Pool, feedHandler *feed.Handler) *http.Server {
	certFile := cfg.CertFile()
	keyFile := cfg.KeyFile()
	certDomain := cfg.CertDomain()
	certCache := cfg.CertCache()
	listenAddr := cfg.ListenAddr()
	server := &http.Server{
		ReadTimeout:  30 * time.Second,
		WriteTimeout: 30 * time.Second,
		IdleTimeout:  60 * time.Second,
		Handler:      setupHandler(cfg, store, feedHandler, pool),
	}

	switch {
	case strings.HasPrefix(listenAddr, "/"):
		startUnixSocketServer(server, listenAddr)
	case certDomain != "" && certCache != "":
		cfg.IsHTTPS = true
		startAutoCertTLSServer(server, certDomain, certCache)
	case certFile != "" && keyFile != "":
		cfg.IsHTTPS = true
		server.Addr = listenAddr
		startTLSServer(server, certFile, keyFile)
	default:
		server.Addr = listenAddr
		startHTTPServer(server)
	}

	return server
}

func startUnixSocketServer(server *http.Server, socketFile string) {
	os.Remove(socketFile)

	go func(sock string) {
		listener, err := net.Listen("unix", sock)
		if err != nil {
			logger.Fatal(`Server failed to start: %v`, err)
		}
		defer listener.Close()

		logger.Info(`Listening on Unix socket %q`, sock)
		if err := server.Serve(listener); err != http.ErrServerClosed {
			logger.Fatal(`Server failed to start: %v`, err)
		}
	}(socketFile)
}

func startAutoCertTLSServer(server *http.Server, certDomain, certCache string) {
	server.Addr = ":https"
	certManager := autocert.Manager{
		Cache:      autocert.DirCache(certCache),
		Prompt:     autocert.AcceptTOS,
		HostPolicy: autocert.HostWhitelist(certDomain),
	}

	// Handle http-01 challenge.
	s := &http.Server{
		Handler: certManager.HTTPHandler(nil),
		Addr:    ":http",
	}
	go s.ListenAndServe()

	go func() {
		logger.Info(`Listening on %q by using auto-configured certificate for %q`, server.Addr, certDomain)
		if err := server.Serve(certManager.Listener()); err != http.ErrServerClosed {
			logger.Fatal(`Server failed to start: %v`, err)
		}
	}()
}

func startTLSServer(server *http.Server, certFile, keyFile string) {
	// See https://blog.cloudflare.com/exposing-go-on-the-internet/
	// And https://wiki.mozilla.org/Security/Server_Side_TLS
	server.TLSConfig = &tls.Config{
		MinVersion:               tls.VersionTLS12,
		PreferServerCipherSuites: true,
		CurvePreferences: []tls.CurveID{
			tls.CurveP256,
			tls.X25519,
		},
		CipherSuites: []uint16{
			tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
			tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
			tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
			tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
			tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
			tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
		},
	}

	go func() {
		logger.Info(`Listening on %q by using certificate %q and key %q`, server.Addr, certFile, keyFile)
		if err := server.ListenAndServeTLS(certFile, keyFile); err != http.ErrServerClosed {
			logger.Fatal(`Server failed to start: %v`, err)
		}
	}()
}

func startHTTPServer(server *http.Server) {
	go func() {
		logger.Info(`Listening on %q without TLS`, server.Addr)
		if err := server.ListenAndServe(); err != http.ErrServerClosed {
			logger.Fatal(`Server failed to start: %v`, err)
		}
	}()
}

func setupHandler(cfg *config.Config, store *storage.Storage, feedHandler *feed.Handler, pool *worker.Pool) *mux.Router {
	router := mux.NewRouter()

	if cfg.BasePath() != "" {
		router = router.PathPrefix(cfg.BasePath()).Subrouter()
	}

	router.Use(newMiddleware(cfg).Serve)

	fever.Serve(router, cfg, store)
	api.Serve(router, store, feedHandler)
	ui.Serve(router, cfg, store, pool, feedHandler)

	return router
}
<commit_msg>Change Unix socket permission to make it accessible from other services<commit_after>// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.

package httpd // import "miniflux.app/service/httpd"

import (
	"crypto/tls"
	"net"
	"net/http"
	"os"
	"strings"
	"time"

	"miniflux.app/api"
	"miniflux.app/config"
	"miniflux.app/fever"
	"miniflux.app/logger"
	"miniflux.app/reader/feed"
	"miniflux.app/storage"
	"miniflux.app/ui"
	"miniflux.app/worker"

	"github.com/gorilla/mux"
	"golang.org/x/crypto/acme/autocert"
)

// Serve starts a new HTTP server.
func Serve(cfg *config.Config, store *storage.Storage, pool *worker.Pool, feedHandler *feed.Handler) *http.Server {
	certFile := cfg.CertFile()
	keyFile := cfg.KeyFile()
	certDomain := cfg.CertDomain()
	certCache := cfg.CertCache()
	listenAddr := cfg.ListenAddr()
	server := &http.Server{
		ReadTimeout:  30 * time.Second,
		WriteTimeout: 30 * time.Second,
		IdleTimeout:  60 * time.Second,
		Handler:      setupHandler(cfg, store, feedHandler, pool),
	}

	switch {
	case strings.HasPrefix(listenAddr, "/"):
		startUnixSocketServer(server, listenAddr)
	case certDomain != "" && certCache != "":
		cfg.IsHTTPS = true
		startAutoCertTLSServer(server, certDomain, certCache)
	case certFile != "" && keyFile != "":
		cfg.IsHTTPS = true
		server.Addr = listenAddr
		startTLSServer(server, certFile, keyFile)
	default:
		server.Addr = listenAddr
		startHTTPServer(server)
	}

	return server
}

func startUnixSocketServer(server *http.Server, socketFile string) {
	os.Remove(socketFile)

	go func(sock string) {
		listener, err := net.Listen("unix", sock)
		if err != nil {
			logger.Fatal(`Server failed to start: %v`, err)
		}
		defer listener.Close()

		if err := os.Chmod(sock, 0666); err != nil {
			logger.Fatal(`Unable to change socket permission: %v`, err)
		}

		logger.Info(`Listening on Unix socket %q`, sock)
		if err := server.Serve(listener); err != http.ErrServerClosed {
			logger.Fatal(`Server failed to start: %v`, err)
		}
	}(socketFile)
}

func startAutoCertTLSServer(server *http.Server, certDomain, certCache string) {
	server.Addr = ":https"
	certManager := autocert.Manager{
		Cache:      autocert.DirCache(certCache),
		Prompt:     autocert.AcceptTOS,
		HostPolicy: autocert.HostWhitelist(certDomain),
	}

	// Handle http-01 challenge.
	s := &http.Server{
		Handler: certManager.HTTPHandler(nil),
		Addr:    ":http",
	}
	go s.ListenAndServe()

	go func() {
		logger.Info(`Listening on %q by using auto-configured certificate for %q`, server.Addr, certDomain)
		if err := server.Serve(certManager.Listener()); err != http.ErrServerClosed {
			logger.Fatal(`Server failed to start: %v`, err)
		}
	}()
}

func startTLSServer(server *http.Server, certFile, keyFile string) {
	// See https://blog.cloudflare.com/exposing-go-on-the-internet/
	// And https://wiki.mozilla.org/Security/Server_Side_TLS
	server.TLSConfig = &tls.Config{
		MinVersion:               tls.VersionTLS12,
		PreferServerCipherSuites: true,
		CurvePreferences: []tls.CurveID{
			tls.CurveP256,
			tls.X25519,
		},
		CipherSuites: []uint16{
			tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
			tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
			tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
			tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
			tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
			tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
		},
	}

	go func() {
		logger.Info(`Listening on %q by using certificate %q and key %q`, server.Addr, certFile, keyFile)
		if err := server.ListenAndServeTLS(certFile, keyFile); err != http.ErrServerClosed {
			logger.Fatal(`Server failed to start: %v`, err)
		}
	}()
}

func startHTTPServer(server *http.Server) {
	go func() {
		logger.Info(`Listening on %q without TLS`, server.Addr)
		if err := server.ListenAndServe(); err != http.ErrServerClosed {
			logger.Fatal(`Server failed to start: %v`, err)
		}
	}()
}

func setupHandler(cfg *config.Config, store *storage.Storage, feedHandler *feed.Handler, pool *worker.Pool) *mux.Router {
	router := mux.NewRouter()

	if cfg.BasePath() != "" {
		router = router.PathPrefix(cfg.BasePath()).Subrouter()
	}

	router.Use(newMiddleware(cfg).Serve)

	fever.Serve(router, cfg, store)
	api.Serve(router, store, feedHandler)
	ui.Serve(router, cfg, store, pool, feedHandler)

	return router
}
<|endoftext|>


<commit_before>// Copyright 2017 The Xorm 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 xorm

import (
	"sync"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
)

func TestUpdateMap(t *testing.T) {
	assert.NoError(t, prepareEngine())

	type UpdateTable struct {
		Id   int64
		Name string
		Age  int
	}

	assert.NoError(t, testEngine.Sync2(new(UpdateTable)))
	var tb = UpdateTable{
		Name: "test",
		Age:  35,
	}
	_, err := testEngine.Insert(&tb)
	assert.NoError(t, err)

	cnt, err := testEngine.Table("update_table").Where("id = ?", tb.Id).Update(map[string]interface{}{
		"name": "test2",
		"age":  36,
	})
	assert.NoError(t, err)
	assert.EqualValues(t, 1, cnt)
}

func TestUpdateLimit(t *testing.T) {
	assert.NoError(t, prepareEngine())

	type UpdateTable struct {
		Id   int64
		Name string
		Age  int
	}

	assert.NoError(t, testEngine.Sync2(new(UpdateTable)))
	var tb = UpdateTable{
		Name: "test1",
		Age:  35,
	}
	cnt, err := testEngine.Insert(&tb)
	assert.NoError(t, err)
	assert.EqualValues(t, 1, cnt)

	tb.Name = "test2"
	tb.Id = 0
	cnt, err = testEngine.Insert(&tb)
	assert.NoError(t, err)
	assert.EqualValues(t, 1, cnt)

	cnt, err = testEngine.OrderBy("name desc").Limit(1).Update(&UpdateTable{
		Age: 30,
	})
	assert.NoError(t, err)
	assert.EqualValues(t, 1, cnt)

	var uts []UpdateTable
	err = testEngine.Find(&uts)
	assert.NoError(t, err)
	assert.EqualValues(t, 2, len(uts))
	assert.EqualValues(t, 35, uts[0].Age)
	assert.EqualValues(t, 30, uts[1].Age)
}

type ForUpdate struct {
	Id   int64 `xorm:"pk"`
	Name string
}

func setupForUpdate(engine *Engine) error {
	v := new(ForUpdate)
	err := engine.DropTables(v)
	if err != nil {
		return err
	}
	err = engine.CreateTables(v)
	if err != nil {
		return err
	}

	list := []ForUpdate{
		{1, "data1"},
		{2, "data2"},
		{3, "data3"},
	}

	for _, f := range list {
		_, err = engine.Insert(f)
		if err != nil {
			return err
		}
	}
	return nil
}

func TestForUpdate(t *testing.T) {
	if testEngine.DriverName() != "mysql" && testEngine.DriverName() != "mymysql" {
		return
	}

	err := setupForUpdate(testEngine)
	if err != nil {
		t.Error(err)
		return
	}

	session1 := testEngine.NewSession()
	session2 := testEngine.NewSession()
	session3 := testEngine.NewSession()
	defer session1.Close()
	defer session2.Close()
	defer session3.Close()

	// start transaction
	err = session1.Begin()
	if err != nil {
		t.Error(err)
		return
	}

	// use lock
	fList := make([]ForUpdate, 0)
	session1.ForUpdate()
	session1.Where("(id) = ?", 1)
	err = session1.Find(&fList)
	switch {
	case err != nil:
		t.Error(err)
		return
	case len(fList) != 1:
		t.Errorf("find not returned single row")
		return
	case fList[0].Name != "data1":
		t.Errorf("for_update.name must be `data1`")
		return
	}

	// wait for lock
	wg := &sync.WaitGroup{}

	// lock is used
	wg.Add(1)
	go func() {
		f2 := new(ForUpdate)
		session2.Where("(id) = ?", 1).ForUpdate()
		has, err := session2.Get(f2) // wait release lock
		switch {
		case err != nil:
			t.Error(err)
		case !has:
			t.Errorf("cannot find target row. for_update.id = 1")
		case f2.Name != "updated by session1":
			t.Errorf("read lock failed")
		}
		wg.Done()
	}()

	// lock is NOT used
	wg.Add(1)
	go func() {
		f3 := new(ForUpdate)
		session3.Where("(id) = ?", 1)
		has, err := session3.Get(f3) // wait release lock
		switch {
		case err != nil:
			t.Error(err)
		case !has:
			t.Errorf("cannot find target row. for_update.id = 1")
		case f3.Name != "data1":
			t.Errorf("read lock failed")
		}
		wg.Done()
	}()

	// wait for go rountines
	time.Sleep(50 * time.Millisecond)

	f := new(ForUpdate)
	f.Name = "updated by session1"
	session1.Where("(id) = ?", 1)
	session1.Update(f)

	// release lock
	err = session1.Commit()
	if err != nil {
		t.Error(err)
		return
	}

	wg.Wait()
}
<commit_msg>add update test for #555 (#598)<commit_after>// Copyright 2017 The Xorm 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 xorm

import (
	"sync"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
)

func TestUpdateMap(t *testing.T) {
	assert.NoError(t, prepareEngine())

	type UpdateTable struct {
		Id   int64
		Name string
		Age  int
	}

	assert.NoError(t, testEngine.Sync2(new(UpdateTable)))
	var tb = UpdateTable{
		Name: "test",
		Age:  35,
	}
	_, err := testEngine.Insert(&tb)
	assert.NoError(t, err)

	cnt, err := testEngine.Table("update_table").Where("id = ?", tb.Id).Update(map[string]interface{}{
		"name": "test2",
		"age":  36,
	})
	assert.NoError(t, err)
	assert.EqualValues(t, 1, cnt)
}

func TestUpdateLimit(t *testing.T) {
	assert.NoError(t, prepareEngine())

	type UpdateTable struct {
		Id   int64
		Name string
		Age  int
	}

	assert.NoError(t, testEngine.Sync2(new(UpdateTable)))
	var tb = UpdateTable{
		Name: "test1",
		Age:  35,
	}
	cnt, err := testEngine.Insert(&tb)
	assert.NoError(t, err)
	assert.EqualValues(t, 1, cnt)

	tb.Name = "test2"
	tb.Id = 0
	cnt, err = testEngine.Insert(&tb)
	assert.NoError(t, err)
	assert.EqualValues(t, 1, cnt)

	cnt, err = testEngine.OrderBy("name desc").Limit(1).Update(&UpdateTable{
		Age: 30,
	})
	assert.NoError(t, err)
	assert.EqualValues(t, 1, cnt)

	var uts []UpdateTable
	err = testEngine.Find(&uts)
	assert.NoError(t, err)
	assert.EqualValues(t, 2, len(uts))
	assert.EqualValues(t, 35, uts[0].Age)
	assert.EqualValues(t, 30, uts[1].Age)
}

type ForUpdate struct {
	Id   int64 `xorm:"pk"`
	Name string
}

func setupForUpdate(engine *Engine) error {
	v := new(ForUpdate)
	err := engine.DropTables(v)
	if err != nil {
		return err
	}
	err = engine.CreateTables(v)
	if err != nil {
		return err
	}

	list := []ForUpdate{
		{1, "data1"},
		{2, "data2"},
		{3, "data3"},
	}

	for _, f := range list {
		_, err = engine.Insert(f)
		if err != nil {
			return err
		}
	}
	return nil
}

func TestForUpdate(t *testing.T) {
	if testEngine.DriverName() != "mysql" && testEngine.DriverName() != "mymysql" {
		return
	}

	err := setupForUpdate(testEngine)
	if err != nil {
		t.Error(err)
		return
	}

	session1 := testEngine.NewSession()
	session2 := testEngine.NewSession()
	session3 := testEngine.NewSession()
	defer session1.Close()
	defer session2.Close()
	defer session3.Close()

	// start transaction
	err = session1.Begin()
	if err != nil {
		t.Error(err)
		return
	}

	// use lock
	fList := make([]ForUpdate, 0)
	session1.ForUpdate()
	session1.Where("(id) = ?", 1)
	err = session1.Find(&fList)
	switch {
	case err != nil:
		t.Error(err)
		return
	case len(fList) != 1:
		t.Errorf("find not returned single row")
		return
	case fList[0].Name != "data1":
		t.Errorf("for_update.name must be `data1`")
		return
	}

	// wait for lock
	wg := &sync.WaitGroup{}

	// lock is used
	wg.Add(1)
	go func() {
		f2 := new(ForUpdate)
		session2.Where("(id) = ?", 1).ForUpdate()
		has, err := session2.Get(f2) // wait release lock
		switch {
		case err != nil:
			t.Error(err)
		case !has:
			t.Errorf("cannot find target row. for_update.id = 1")
		case f2.Name != "updated by session1":
			t.Errorf("read lock failed")
		}
		wg.Done()
	}()

	// lock is NOT used
	wg.Add(1)
	go func() {
		f3 := new(ForUpdate)
		session3.Where("(id) = ?", 1)
		has, err := session3.Get(f3) // wait release lock
		switch {
		case err != nil:
			t.Error(err)
		case !has:
			t.Errorf("cannot find target row. for_update.id = 1")
		case f3.Name != "data1":
			t.Errorf("read lock failed")
		}
		wg.Done()
	}()

	// wait for go rountines
	time.Sleep(50 * time.Millisecond)

	f := new(ForUpdate)
	f.Name = "updated by session1"
	session1.Where("(id) = ?", 1)
	session1.Update(f)

	// release lock
	err = session1.Commit()
	if err != nil {
		t.Error(err)
		return
	}

	wg.Wait()
}

func TestWithIn(t *testing.T) {
	type temp3 struct {
		Id   int64  `xorm:"Id pk autoincr"`
		Name string `xorm:"Name"`
		Test bool   `xorm:"Test"`
	}

	assert.NoError(t, prepareEngine())
	assert.NoError(t, testEngine.Sync(new(temp3)))

	testEngine.Insert(&[]temp3{
		{
			Name: "user1",
		},
		{
			Name: "user1",
		},
		{
			Name: "user1",
		},
	})

	cnt, err := testEngine.In("Id", 1, 2, 3, 4).Update(&temp3{Name: "aa"}, &temp3{Name: "user1"})
	assert.NoError(t, err)
	assert.EqualValues(t, 3, cnt)
}
<|endoftext|>


<commit_before>package main

import (
    "fmt"
    "encoding/json"
    "encoding/xml"
)

const (
    _ = iota
    TakeBook
    ReturnBook
    GetAvailability
)

type SimpleLibrary struct {
    Books map[string]*Book
    registeredCopyCount map[string]int
    availableCopyCount map[string]int
    librarians chan struct{}
}

type BookError struct {
    ISBN string
}

type TooManyCopiesBookError struct {
    BookError
}

type NotFoundBookError struct {
    BookError
}

type NotAvaiableBookError struct {
    BookError
}

type AllCopiesAvailableBookError struct {
    BookError
}

func (l *Library) MarshalJSON() ([]byte, error) {
    return json.Marshal(l)
}

func (l *Library) UnmarshalJSON(data []byte) error {
    return json.Unmarshal(data, l)
}

func (l *Library) MarshalXML(e *Encoder, start StartElement) error {
    return e.EncodeElement(l, start)
}

func (l *Library) UnmarshalXML(d *Decoder, start StartElement) error {
    return d.DecodeElement(l, &start)
}

func (e *TooManyCopiesBookError) Error() string {
    return fmt.Sprintf("Има 4 копия на книга %v", e.ISBN)
}

func (e *NotFoundBookError) Error() string {
    return fmt.Sprintf("Непозната книга %v", e.ISBN)
}

func (e *NotAvailableBookError) Error() string {
    return fmt.Sprintf("Няма наличност на книга %v", e.ISBN)
}

func (e *AllCopiesAvailableBookError) Error() string {
    return fmt.Sprintf("Всички копия са налични %v", e.ISBN)
}

func (sl *SimpleLibrary) addBook(book *Book) (registeredCopyCount int, err error) {
    if sl.registeredCopyCount[book.ISBN] >= 4 {
        err = TooManyCopiesBookError{ISBN: book.ISBN}
    } else {
        sl.Books[book.ISBN] = book
        sl.registeredCopyCount[book.ISBN]++
        sl.availableCopyCount[book.ISBN]++
        registeredCopyCount = sl.registeredCopyCount
    }

    return
}

func (sl *SimpleLibrary) AddBookJSON(data []byte) (int, error) {
    var book *Book
    json.Unmarshal(data, book)
    return el.addBook(book)
}

func (sl *SimpleLibrary) AddBookXML(data []byte) (int, error) {
    var book *Book
    xml.Unmarshal(data, book)
    return el.addBook(book)
}

func (sl *SimpleLibrary) Hello() (requests chan<- LibraryRequest, responses <-chan LibraryResponse) {
    requests = make(chan<- LibraryRequest)
    responses = make(<-chan LibraryResponse)

    go func() {
        for request := range requests {
            <-sl.librarians
            isbn := request.GetISBN()
            response := new(SimpleLibraryResponse)

            switch request.GetType() {
            case TakeBook:
                if book, isBookRegistered := sl.Books[isbn]; isBookRegistered && sl.availableCopyCount[isbn] > 0 {
                    response.book = book
                    sl.availableCopyCount[isbn]--
                } else if !isBookRegistered {
                    response.err = &NotFoundBookError{BookError{isbn}}
                } else {
                    response.err = &NotAvailableBookError{BookError{isbn}}
                }

            case ReturnBook:
                if _, isBookRegistered := sl.Books[isbn]; isBookRegistered && sl.availableCopyCount[isbn] < sl.registeredCopyCount[isbn] {
                    sl.availableCopyCount[isbn]++
                } else if !isBookRegistered {
                    response.err = &NotFoundBookError{BookError{isbn}}
                } else {
                    response.err = &AllCopiesAvailableBookError{BookError{isbn}}
                }

            case GetAvailability:
                response.registeredCopyCount = sl.registeredCopyCount[isbn]
                response.availableCopyCount = sl.availableCopyCount[isbn]
            }

            responses <- response
            sl.librarians <- struct{}{}
        }
    }()

    return
}

func NewLibrary(librarians int) Library {
    return &SimpleLibrary{
        Books: make(map[string]*Book),
        registeredCopyCount: make(map[string]int),
        availableCopyCount: make(map[string]int),
        librarians: make(chan struct{}, librarians),
    }
}
<commit_msg>fix: typo<commit_after>package main

import (
    "fmt"
    "encoding/json"
    "encoding/xml"
)

const (
    _ = iota
    TakeBook
    ReturnBook
    GetAvailability
)

type SimpleLibrary struct {
    Books map[string]*Book
    registeredCopyCount map[string]int
    availableCopyCount map[string]int
    librarians chan struct{}
}

type BookError struct {
    ISBN string
}

type TooManyCopiesBookError struct {
    BookError
}

type NotFoundBookError struct {
    BookError
}

type NotAvailableBookError struct {
    BookError
}

type AllCopiesAvailableBookError struct {
    BookError
}

func (l *Library) MarshalJSON() ([]byte, error) {
    return json.Marshal(l)
}

func (l *Library) UnmarshalJSON(data []byte) error {
    return json.Unmarshal(data, l)
}

func (l *Library) MarshalXML(e *Encoder, start StartElement) error {
    return e.EncodeElement(l, start)
}

func (l *Library) UnmarshalXML(d *Decoder, start StartElement) error {
    return d.DecodeElement(l, &start)
}

func (e *TooManyCopiesBookError) Error() string {
    return fmt.Sprintf("Има 4 копия на книга %v", e.ISBN)
}

func (e *NotFoundBookError) Error() string {
    return fmt.Sprintf("Непозната книга %v", e.ISBN)
}

func (e *NotAvailableBookError) Error() string {
    return fmt.Sprintf("Няма наличност на книга %v", e.ISBN)
}

func (e *AllCopiesAvailableBookError) Error() string {
    return fmt.Sprintf("Всички копия са налични %v", e.ISBN)
}

func (sl *SimpleLibrary) addBook(book *Book) (registeredCopyCount int, err error) {
    if sl.registeredCopyCount[book.ISBN] >= 4 {
        err = TooManyCopiesBookError{ISBN: book.ISBN}
    } else {
        sl.Books[book.ISBN] = book
        sl.registeredCopyCount[book.ISBN]++
        sl.availableCopyCount[book.ISBN]++
        registeredCopyCount = sl.registeredCopyCount
    }

    return
}

func (sl *SimpleLibrary) AddBookJSON(data []byte) (int, error) {
    var book *Book
    json.Unmarshal(data, book)
    return el.addBook(book)
}

func (sl *SimpleLibrary) AddBookXML(data []byte) (int, error) {
    var book *Book
    xml.Unmarshal(data, book)
    return el.addBook(book)
}

func (sl *SimpleLibrary) Hello() (requests chan<- LibraryRequest, responses <-chan LibraryResponse) {
    requests = make(chan<- LibraryRequest)
    responses = make(<-chan LibraryResponse)

    go func() {
        for request := range requests {
            <-sl.librarians
            isbn := request.GetISBN()
            response := new(SimpleLibraryResponse)

            switch request.GetType() {
            case TakeBook:
                if book, isBookRegistered := sl.Books[isbn]; isBookRegistered && sl.availableCopyCount[isbn] > 0 {
                    response.book = book
                    sl.availableCopyCount[isbn]--
                } else if !isBookRegistered {
                    response.err = &NotFoundBookError{BookError{isbn}}
                } else {
                    response.err = &NotAvailableBookError{BookError{isbn}}
                }

            case ReturnBook:
                if _, isBookRegistered := sl.Books[isbn]; isBookRegistered && sl.availableCopyCount[isbn] < sl.registeredCopyCount[isbn] {
                    sl.availableCopyCount[isbn]++
                } else if !isBookRegistered {
                    response.err = &NotFoundBookError{BookError{isbn}}
                } else {
                    response.err = &AllCopiesAvailableBookError{BookError{isbn}}
                }

            case GetAvailability:
                response.registeredCopyCount = sl.registeredCopyCount[isbn]
                response.availableCopyCount = sl.availableCopyCount[isbn]
            }

            responses <- response
            sl.librarians <- struct{}{}
        }
    }()

    return
}

func NewLibrary(librarians int) Library {
    return &SimpleLibrary{
        Books: make(map[string]*Book),
        registeredCopyCount: make(map[string]int),
        availableCopyCount: make(map[string]int),
        librarians: make(chan struct{}, librarians),
    }
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"
	"os/signal"
	"strings"
	"syscall"

	uuid "github.com/satori/go.uuid"
	"github.com/underlx/disturbancesmlx/dataobjects"

	"github.com/underlx/disturbancesmlx/discordbot"
)

// DiscordBot starts the Discord bot if it is enabled in the settings
func DiscordBot() {
	discordBox, present := secrets.GetBox("discord")
	if !present {
		discordLog.Println("Discord Keybox not found, Discord functions disabled")
		return
	}

	webKeybox, present := secrets.GetBox("web")
	if !present {
		discordLog.Fatal("Web keybox not present in keybox")
	}

	url, present := webKeybox.Get("websiteURL")
	if !present {
		discordLog.Fatal("Website URL not present in keybox")
	}

	err := discordbot.Start(rootSqalxNode, url, discordBox, discordLog,
		new(BotCommandReceiver))
	if err != nil {
		discordLog.Println(err)
		return
	}

	// Wait here until CTRL-C or other term signal is received.
	discordLog.Println("Bot is now running.")
	sc := make(chan os.Signal, 1)
	signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt)
	<-sc

	// Cleanly close down the Discord session.
	discordbot.Stop()

	os.Exit(0)
}

// BotCommandReceiver implements discordbot.CommandReceiver
type BotCommandReceiver struct{}

// NewLineStatus is called when the bot wants to add a new line status
func (r *BotCommandReceiver) NewLineStatus(status *dataobjects.Status) {
	handleNewStatusNotify(status)
}

// ControlScraper is called when the bot wants to start/stop/change a scraper
func (r *BotCommandReceiver) ControlScraper(scraper string, enable bool, messageCallback func(message string)) {
	handleControlScraper(scraper, enable, messageCallback)
}

// ControlNotifs is caled when the bot wants to block/unblock sending of push notifications
func (r *BotCommandReceiver) ControlNotifs(notifType string, enable bool) {
	handleControlNotifs(notifType, enable)
}

// CastDisturbanceVote is called when the bot wants to cast a disturbance vote
func (r *BotCommandReceiver) CastDisturbanceVote(line *dataobjects.Line, weight int) {
	err := reportHandler.addReport(dataobjects.NewLineDisturbanceReportDebug(line, "discord"), weight)
	if err != nil {
		discordLog.Println(err)
	}
}

// ClearDisturbanceVotes is called when the bot wants to clear disturbance votes
func (r *BotCommandReceiver) ClearDisturbanceVotes(line *dataobjects.Line) {
	reportHandler.clearVotesForLine(line)
}

// GetDisturbanceVotes is called when the bot wants to show current disturbance report status
func (r *BotCommandReceiver) GetDisturbanceVotes(messageCallback func(message string)) {
	message := ""
	lines, err := dataobjects.GetLines(rootSqalxNode)
	if err != nil {
		discordLog.Println(err)
	}
	for _, line := range lines {
		message += fmt.Sprintf("`%s`: %d/%d\n", line.ID, reportHandler.countVotesForLine(line), reportHandler.getThresholdForLine(line))
	}
	messageCallback(message)
}

// GetThresholdMultiplier is called when the bot wants to know the current vote threshold multiplier
func (r *BotCommandReceiver) GetThresholdMultiplier() float32 {
	return reportHandler.ThresholdMultiplier()
}

// SetThresholdMultiplier is called when the bot wants to set the current vote threshold multiplier
func (r *BotCommandReceiver) SetThresholdMultiplier(multiplier float32) {
	reportHandler.SetThresholdMultiplier(multiplier)
}

// GetThresholdOffset is called when the bot wants to know the current vote threshold offset
func (r *BotCommandReceiver) GetThresholdOffset() int {
	return reportHandler.ThresholdOffset()
}

// SetThresholdOffset is called when the bot wants to set the current vote threshold offset
func (r *BotCommandReceiver) SetThresholdOffset(offset int) {
	reportHandler.SetThresholdOffset(offset)
}

// GetVersion is called when the bot wants to get the current server version
func (r *BotCommandReceiver) GetVersion() (gitCommit string, buildDate string) {
	return GitCommit, BuildDate
}

// GetStats is called when the bot wants to get the current server stats
func (r *BotCommandReceiver) GetStats() (dbOpenConnections, apiTotalRequests int) {
	return apiTotalRequests, rdb.Stats().OpenConnections
}

// SchedulesToLines turns the provided schedule array into a human-readable list of strings
func (r *BotCommandReceiver) SchedulesToLines(schedules []*dataobjects.LobbySchedule) []string {
	return schedulesToLines(schedules)
}

// SendNotificationMetaBroadcast sends a FCM message containing a notification to show on some/all clients
func (r *BotCommandReceiver) SendNotificationMetaBroadcast(versionFilter, localeFilter, title, body, url string) {
	id, err := uuid.NewV4()
	if err != nil {
		return
	}

	SendMetaBroadcast(id.String(), "notification", versionFilter, localeFilter,
		[2]string{"title", title},
		[2]string{"body", body},
		[2]string{"url", url})
}

// SendCommandMetaBroadcast sends a FCM message containing a command to run on some/all clients
func (r *BotCommandReceiver) SendCommandMetaBroadcast(versionFilter, localeFilter, command string, args ...string) {
	id, err := uuid.NewV4()
	if err != nil {
		return
	}

	SendMetaBroadcast(id.String(), "command", versionFilter, localeFilter,
		[2]string{"command", command},
		[2]string{"args", strings.Join(args, "|")})
}
<commit_msg>Discord bot: fix bug in GetStats<commit_after>package main

import (
	"fmt"
	"os"
	"os/signal"
	"strings"
	"syscall"

	uuid "github.com/satori/go.uuid"
	"github.com/underlx/disturbancesmlx/dataobjects"

	"github.com/underlx/disturbancesmlx/discordbot"
)

// DiscordBot starts the Discord bot if it is enabled in the settings
func DiscordBot() {
	discordBox, present := secrets.GetBox("discord")
	if !present {
		discordLog.Println("Discord Keybox not found, Discord functions disabled")
		return
	}

	webKeybox, present := secrets.GetBox("web")
	if !present {
		discordLog.Fatal("Web keybox not present in keybox")
	}

	url, present := webKeybox.Get("websiteURL")
	if !present {
		discordLog.Fatal("Website URL not present in keybox")
	}

	err := discordbot.Start(rootSqalxNode, url, discordBox, discordLog,
		new(BotCommandReceiver))
	if err != nil {
		discordLog.Println(err)
		return
	}

	// Wait here until CTRL-C or other term signal is received.
	discordLog.Println("Bot is now running.")
	sc := make(chan os.Signal, 1)
	signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt)
	<-sc

	// Cleanly close down the Discord session.
	discordbot.Stop()

	os.Exit(0)
}

// BotCommandReceiver implements discordbot.CommandReceiver
type BotCommandReceiver struct{}

// NewLineStatus is called when the bot wants to add a new line status
func (r *BotCommandReceiver) NewLineStatus(status *dataobjects.Status) {
	handleNewStatusNotify(status)
}

// ControlScraper is called when the bot wants to start/stop/change a scraper
func (r *BotCommandReceiver) ControlScraper(scraper string, enable bool, messageCallback func(message string)) {
	handleControlScraper(scraper, enable, messageCallback)
}

// ControlNotifs is caled when the bot wants to block/unblock sending of push notifications
func (r *BotCommandReceiver) ControlNotifs(notifType string, enable bool) {
	handleControlNotifs(notifType, enable)
}

// CastDisturbanceVote is called when the bot wants to cast a disturbance vote
func (r *BotCommandReceiver) CastDisturbanceVote(line *dataobjects.Line, weight int) {
	err := reportHandler.addReport(dataobjects.NewLineDisturbanceReportDebug(line, "discord"), weight)
	if err != nil {
		discordLog.Println(err)
	}
}

// ClearDisturbanceVotes is called when the bot wants to clear disturbance votes
func (r *BotCommandReceiver) ClearDisturbanceVotes(line *dataobjects.Line) {
	reportHandler.clearVotesForLine(line)
}

// GetDisturbanceVotes is called when the bot wants to show current disturbance report status
func (r *BotCommandReceiver) GetDisturbanceVotes(messageCallback func(message string)) {
	message := ""
	lines, err := dataobjects.GetLines(rootSqalxNode)
	if err != nil {
		discordLog.Println(err)
	}
	for _, line := range lines {
		message += fmt.Sprintf("`%s`: %d/%d\n", line.ID, reportHandler.countVotesForLine(line), reportHandler.getThresholdForLine(line))
	}
	messageCallback(message)
}

// GetThresholdMultiplier is called when the bot wants to know the current vote threshold multiplier
func (r *BotCommandReceiver) GetThresholdMultiplier() float32 {
	return reportHandler.ThresholdMultiplier()
}

// SetThresholdMultiplier is called when the bot wants to set the current vote threshold multiplier
func (r *BotCommandReceiver) SetThresholdMultiplier(multiplier float32) {
	reportHandler.SetThresholdMultiplier(multiplier)
}

// GetThresholdOffset is called when the bot wants to know the current vote threshold offset
func (r *BotCommandReceiver) GetThresholdOffset() int {
	return reportHandler.ThresholdOffset()
}

// SetThresholdOffset is called when the bot wants to set the current vote threshold offset
func (r *BotCommandReceiver) SetThresholdOffset(offset int) {
	reportHandler.SetThresholdOffset(offset)
}

// GetVersion is called when the bot wants to get the current server version
func (r *BotCommandReceiver) GetVersion() (gitCommit string, buildDate string) {
	return GitCommit, BuildDate
}

// GetStats is called when the bot wants to get the current server stats
func (r *BotCommandReceiver) GetStats() (dbOpenConnections, apiTR int) {
	return rdb.Stats().OpenConnections, apiTotalRequests
}

// SchedulesToLines turns the provided schedule array into a human-readable list of strings
func (r *BotCommandReceiver) SchedulesToLines(schedules []*dataobjects.LobbySchedule) []string {
	return schedulesToLines(schedules)
}

// SendNotificationMetaBroadcast sends a FCM message containing a notification to show on some/all clients
func (r *BotCommandReceiver) SendNotificationMetaBroadcast(versionFilter, localeFilter, title, body, url string) {
	id, err := uuid.NewV4()
	if err != nil {
		return
	}

	SendMetaBroadcast(id.String(), "notification", versionFilter, localeFilter,
		[2]string{"title", title},
		[2]string{"body", body},
		[2]string{"url", url})
}

// SendCommandMetaBroadcast sends a FCM message containing a command to run on some/all clients
func (r *BotCommandReceiver) SendCommandMetaBroadcast(versionFilter, localeFilter, command string, args ...string) {
	id, err := uuid.NewV4()
	if err != nil {
		return
	}

	SendMetaBroadcast(id.String(), "command", versionFilter, localeFilter,
		[2]string{"command", command},
		[2]string{"args", strings.Join(args, "|")})
}
<|endoftext|>


<commit_before>/*
 * MumbleDJ
 * By Matthieu Grieger
 * bot/queue.go
 * Copyright (c) 2016 Matthieu Grieger (MIT License)
 */

package bot

import (
	"errors"
	"fmt"
	"math/rand"
	"os"
	"sync"
	"time"

	"github.com/layeh/gumble/gumbleffmpeg"
	_ "github.com/layeh/gumble/opus"
	"github.com/matthieugrieger/mumbledj/interfaces"
	"github.com/spf13/viper"
)

// Queue holds the audio queue itself along with useful methods for
// performing actions on the queue.
type Queue struct {
	Queue []interfaces.Track
	mutex sync.RWMutex
}

func init() {
	rand.Seed(time.Now().UTC().UnixNano())
}

// NewQueue initializes a new queue and returns it.
func NewQueue() *Queue {
	return &Queue{
		Queue: make([]interfaces.Track, 0),
	}
}

// Length returns the length of the queue.
func (q *Queue) Length() int {
	q.mutex.RLock()
	length := len(q.Queue)
	q.mutex.RUnlock()
	return length
}

// Reset removes all tracks from the queue.
func (q *Queue) Reset() {
	q.mutex.Lock()
	q.Queue = q.Queue[:0]
	q.mutex.Unlock()
}

// AppendTrack adds a track to the back of the queue.
func (q *Queue) AppendTrack(t interfaces.Track) error {
	q.mutex.Lock()
	beforeLen := len(q.Queue)

	// An error should never occur here since maxTrackDuration is restricted to
	// ints. Any error in the configuration will be caught during yaml load.
	maxTrackDuration, _ := time.ParseDuration(fmt.Sprintf("%ds",
		viper.GetInt("queue.max_track_duration")))

	if viper.GetInt("queue.max_track_duration") == 0 ||
		t.GetDuration() <= maxTrackDuration {
		q.Queue = append(q.Queue, t)
	} else {
		q.mutex.Unlock()
		return errors.New("The track is too long to add to the queue")
	}
	if len(q.Queue) == beforeLen+1 {
		q.mutex.Unlock()
		q.playIfNeeded()
		return nil
	}
	q.mutex.Unlock()
	return errors.New("Could not add track to queue")
}

// InsertTrack inserts track `t` at position `i` in the queue.
func (q *Queue) InsertTrack(i int, t interfaces.Track) error {
	q.mutex.Lock()
	beforeLen := len(q.Queue)

	// An error should never occur here since maxTrackDuration is restricted to
	// ints. Any error in the configuration will be caught during yaml load.
	maxTrackDuration, _ := time.ParseDuration(fmt.Sprintf("%ds",
		viper.GetInt("queue.max_track_duration")))

	if viper.GetInt("queue.max_track_duration") == 0 ||
		t.GetDuration() <= maxTrackDuration {
		q.Queue = append(q.Queue, Track{})
		copy(q.Queue[i+1:], q.Queue[i:])
		q.Queue[i] = t
	} else {
		q.mutex.Unlock()
		return errors.New("The track is too long to add to the queue")
	}
	if len(q.Queue) == beforeLen+1 {
		q.mutex.Unlock()
		q.playIfNeeded()
		return nil
	}
	q.mutex.Unlock()
	return errors.New("Could not add track to queue")
}

// CurrentTrack returns the current Track.
func (q *Queue) CurrentTrack() (interfaces.Track, error) {
	q.mutex.RLock()
	if len(q.Queue) != 0 {
		current := q.Queue[0]
		q.mutex.RUnlock()
		return current, nil
	}
	q.mutex.RUnlock()
	return nil, errors.New("There are no tracks currently in the queue")
}

// GetTrack takes an `index` argument to determine which track to return.
// If the track in position `index` exists, it is returned. Otherwise,
// nil is returned.
func (q *Queue) GetTrack(index int) interfaces.Track {
	q.mutex.RLock()
	if index >= len(q.Queue) {
		q.mutex.RUnlock()
		return nil
	}
	track := q.Queue[index]
	q.mutex.RUnlock()
	return track
}

// PeekNextTrack peeks at the next track and returns it.
func (q *Queue) PeekNextTrack() (interfaces.Track, error) {
	q.mutex.RLock()
	if len(q.Queue) > 1 {
		if viper.GetBool("queue.automatic_shuffle_on") {
			q.RandomNextTrack(false)
		}
		next := q.Queue[1]
		q.mutex.RUnlock()
		return next, nil
	}
	q.mutex.RUnlock()
	return nil, errors.New("There is no track coming up next")
}

// Traverse is a traversal function for Queue. Allows a visit function to
// be passed in which performs the specified action on each queue item.
func (q *Queue) Traverse(visit func(i int, t interfaces.Track)) {
	q.mutex.RLock()
	if len(q.Queue) > 0 {
		for queueIndex, queueTrack := range q.Queue {
			visit(queueIndex, queueTrack)
		}
	}
	q.mutex.RUnlock()
}

// ShuffleTracks shuffles the queue using an inside-out algorithm.
func (q *Queue) ShuffleTracks() {
	q.mutex.Lock()
	// Skip the first track, as it is likely playing.
	for i := range q.Queue[1:] {
		j := rand.Intn(i + 1)
		q.Queue[i+1], q.Queue[j+1] = q.Queue[j+1], q.Queue[i+1]
	}
	q.mutex.Unlock()
}

// RandomNextTrack sets a random track as the next track to be played.
func (q *Queue) RandomNextTrack(queueWasEmpty bool) {
	q.mutex.Lock()
	if len(q.Queue) > 1 {
		nextTrackIndex := 1
		if queueWasEmpty {
			nextTrackIndex = 0
		}
		swapIndex := nextTrackIndex + rand.Intn(len(q.Queue)-1)
		q.Queue[nextTrackIndex], q.Queue[swapIndex] = q.Queue[swapIndex], q.Queue[nextTrackIndex]
	}
	q.mutex.Unlock()
}

// Skip performs the necessary actions that take place when a track is skipped
// via a command.
func (q *Queue) Skip() {
	// Stop audio stream if one exists.
	if DJ.AudioStream != nil {
		q.StopCurrent()
		DJ.AudioStream = nil
	}

	// Remove all track skips.
	DJ.Skips.ResetTrackSkips()

	q.mutex.Lock()
	// If caching is disabled, delete the track from disk.
	if !viper.GetBool("cache.enabled") {
		DJ.YouTubeDL.Delete(q.Queue[0])
	}

	// If automatic track shuffling is enabled, assign a random track in the queue to be the next track.
	if viper.GetBool("queue.automatic_shuffle_on") {
		q.mutex.Unlock()
		q.RandomNextTrack(false)
		q.mutex.Lock()
	}

	// Remove all playlist skips if this is the last track of the playlist still in the queue.
	if playlist := q.Queue[0].GetPlaylist(); playlist != nil {
		id := playlist.GetID()
		playlistIsFinished := true

		q.mutex.Unlock()
		q.Traverse(func(i int, t interfaces.Track) {
			if i != 0 && t.GetPlaylist() != nil {
				if t.GetPlaylist().GetID() == id {
					playlistIsFinished = false
				}
			}
		})
		q.mutex.Lock()

		if playlistIsFinished {
			DJ.Skips.ResetPlaylistSkips()
		}
	}

	// Skip the track.
	length := len(q.Queue)
	if length > 1 {
		q.Queue = q.Queue[1:]
	} else {
		q.Queue = make([]interfaces.Track, 0)
	}
	q.mutex.Unlock()

	if err := q.playIfNeeded(); err != nil {
		q.Skip()
	}
}

// SkipPlaylist performs the necessary actions that take place when a playlist
// is skipped via a command.
func (q *Queue) SkipPlaylist() {
	q.mutex.Lock()
	if playlist := q.Queue[0].GetPlaylist(); playlist != nil {
		currentPlaylistID := playlist.GetID()

		// We must loop backwards to prevent missing any elements after deletion.
		// NOTE: We do not remove the first track of the playlist quite yet as that
		// is removed properly with the following Skip() call.
		for i := len(q.Queue) - 1; i >= 1; i-- {
			if otherTrackPlaylist := q.Queue[i].GetPlaylist(); otherTrackPlaylist != nil {
				if otherTrackPlaylist.GetID() == currentPlaylistID {
					q.Queue = append(q.Queue[:i], q.Queue[i+1:]...)
				}
			}
		}
	}
	q.mutex.Unlock()
	q.StopCurrent()
}

// PlayCurrent creates a new audio stream and begins playing the current track.
func (q *Queue) PlayCurrent() error {
	currentTrack := q.GetTrack(0)
	filepath := os.ExpandEnv(viper.GetString("cache.directory") + "/" + currentTrack.GetFilename())
	if _, err := os.Stat(filepath); os.IsNotExist(err) {
		if err := DJ.YouTubeDL.Download(q.GetTrack(0)); err != nil {
			return err
		}
	}
	source := gumbleffmpeg.SourceFile(filepath)
	DJ.AudioStream = gumbleffmpeg.New(DJ.Client, source)
	DJ.AudioStream.Offset = currentTrack.GetPlaybackOffset()
	DJ.AudioStream.Volume = DJ.Volume

	if viper.GetString("defaults.player_command") == "avconv" {
		DJ.AudioStream.Command = "avconv"
	}

	if viper.GetBool("queue.announce_new_tracks") {
		message :=
			`<table

					%s (%s)

					Added by %s

			`
		message = fmt.Sprintf(message, currentTrack.GetThumbnailURL(), currentTrack.GetURL(),
			currentTrack.GetTitle(), currentTrack.GetDuration().String(), currentTrack.GetSubmitter())
		if currentTrack.GetPlaylist() != nil {
			message = fmt.Sprintf(message+`<tr><td align="center">From playlist "%s"</td></tr>`, currentTrack.GetPlaylist().GetTitle())
		}
		message += `</table>`
		DJ.Client.Self.Channel.Send(message, false)
	}

	DJ.AudioStream.Play()
	go func() {
		DJ.AudioStream.Wait()
		q.Skip()
	}()

	return nil
}

// PauseCurrent pauses the current audio stream if it exists and is not already paused.
func (q *Queue) PauseCurrent() error {
	if DJ.AudioStream == nil {
		return errors.New("There is no track to pause")
	}
	if DJ.AudioStream.State() == gumbleffmpeg.StatePaused {
		return errors.New("The track is already paused")
	}
	DJ.AudioStream.Pause()
	return nil
}

// ResumeCurrent resumes playback of the current audio stream if it exists and is paused.
func (q *Queue) ResumeCurrent() error {
	if DJ.AudioStream == nil {
		return errors.New("There is no track to resume")
	}
	if DJ.AudioStream.State() == gumbleffmpeg.StatePlaying {
		return errors.New("The track is already playing")
	}
	DJ.AudioStream.Play()
	return nil
}

// StopCurrent stops the playback of the current audio stream if it exists.
func (q *Queue) StopCurrent() error {
	if DJ.AudioStream == nil {
		return errors.New("The audio stream is nil")
	}
	DJ.AudioStream.Stop()
	DJ.AudioStream = nil
	return nil
}

func (q *Queue) playIfNeeded() error {
	if DJ.AudioStream == nil && q.Length() > 0 {
		if err := DJ.YouTubeDL.Download(q.GetTrack(0)); err != nil {
			return err
		}
		if err := q.PlayCurrent(); err != nil {
			return err
		}
	}
	return nil
}
<commit_msg>Fixed typo on table HTML tag<commit_after>/*
 * MumbleDJ
 * By Matthieu Grieger
 * bot/queue.go
 * Copyright (c) 2016 Matthieu Grieger (MIT License)
 */

package bot

import (
	"errors"
	"fmt"
	"math/rand"
	"os"
	"sync"
	"time"

	"github.com/layeh/gumble/gumbleffmpeg"
	_ "github.com/layeh/gumble/opus"
	"github.com/matthieugrieger/mumbledj/interfaces"
	"github.com/spf13/viper"
)

// Queue holds the audio queue itself along with useful methods for
// performing actions on the queue.
type Queue struct {
	Queue []interfaces.Track
	mutex sync.RWMutex
}

func init() {
	rand.Seed(time.Now().UTC().UnixNano())
}

// NewQueue initializes a new queue and returns it.
func NewQueue() *Queue {
	return &Queue{
		Queue: make([]interfaces.Track, 0),
	}
}

// Length returns the length of the queue.
func (q *Queue) Length() int {
	q.mutex.RLock()
	length := len(q.Queue)
	q.mutex.RUnlock()
	return length
}

// Reset removes all tracks from the queue.
func (q *Queue) Reset() {
	q.mutex.Lock()
	q.Queue = q.Queue[:0]
	q.mutex.Unlock()
}

// AppendTrack adds a track to the back of the queue.
func (q *Queue) AppendTrack(t interfaces.Track) error {
	q.mutex.Lock()
	beforeLen := len(q.Queue)

	// An error should never occur here since maxTrackDuration is restricted to
	// ints. Any error in the configuration will be caught during yaml load.
	maxTrackDuration, _ := time.ParseDuration(fmt.Sprintf("%ds",
		viper.GetInt("queue.max_track_duration")))

	if viper.GetInt("queue.max_track_duration") == 0 ||
		t.GetDuration() <= maxTrackDuration {
		q.Queue = append(q.Queue, t)
	} else {
		q.mutex.Unlock()
		return errors.New("The track is too long to add to the queue")
	}
	if len(q.Queue) == beforeLen+1 {
		q.mutex.Unlock()
		q.playIfNeeded()
		return nil
	}
	q.mutex.Unlock()
	return errors.New("Could not add track to queue")
}

// InsertTrack inserts track `t` at position `i` in the queue.
func (q *Queue) InsertTrack(i int, t interfaces.Track) error {
	q.mutex.Lock()
	beforeLen := len(q.Queue)

	// An error should never occur here since maxTrackDuration is restricted to
	// ints. Any error in the configuration will be caught during yaml load.
	maxTrackDuration, _ := time.ParseDuration(fmt.Sprintf("%ds",
		viper.GetInt("queue.max_track_duration")))

	if viper.GetInt("queue.max_track_duration") == 0 ||
		t.GetDuration() <= maxTrackDuration {
		q.Queue = append(q.Queue, Track{})
		copy(q.Queue[i+1:], q.Queue[i:])
		q.Queue[i] = t
	} else {
		q.mutex.Unlock()
		return errors.New("The track is too long to add to the queue")
	}
	if len(q.Queue) == beforeLen+1 {
		q.mutex.Unlock()
		q.playIfNeeded()
		return nil
	}
	q.mutex.Unlock()
	return errors.New("Could not add track to queue")
}

// CurrentTrack returns the current Track.
func (q *Queue) CurrentTrack() (interfaces.Track, error) {
	q.mutex.RLock()
	if len(q.Queue) != 0 {
		current := q.Queue[0]
		q.mutex.RUnlock()
		return current, nil
	}
	q.mutex.RUnlock()
	return nil, errors.New("There are no tracks currently in the queue")
}

// GetTrack takes an `index` argument to determine which track to return.
// If the track in position `index` exists, it is returned. Otherwise,
// nil is returned.
func (q *Queue) GetTrack(index int) interfaces.Track {
	q.mutex.RLock()
	if index >= len(q.Queue) {
		q.mutex.RUnlock()
		return nil
	}
	track := q.Queue[index]
	q.mutex.RUnlock()
	return track
}

// PeekNextTrack peeks at the next track and returns it.
func (q *Queue) PeekNextTrack() (interfaces.Track, error) {
	q.mutex.RLock()
	if len(q.Queue) > 1 {
		if viper.GetBool("queue.automatic_shuffle_on") {
			q.RandomNextTrack(false)
		}
		next := q.Queue[1]
		q.mutex.RUnlock()
		return next, nil
	}
	q.mutex.RUnlock()
	return nil, errors.New("There is no track coming up next")
}

// Traverse is a traversal function for Queue. Allows a visit function to
// be passed in which performs the specified action on each queue item.
func (q *Queue) Traverse(visit func(i int, t interfaces.Track)) {
	q.mutex.RLock()
	if len(q.Queue) > 0 {
		for queueIndex, queueTrack := range q.Queue {
			visit(queueIndex, queueTrack)
		}
	}
	q.mutex.RUnlock()
}

// ShuffleTracks shuffles the queue using an inside-out algorithm.
func (q *Queue) ShuffleTracks() {
	q.mutex.Lock()
	// Skip the first track, as it is likely playing.
	for i := range q.Queue[1:] {
		j := rand.Intn(i + 1)
		q.Queue[i+1], q.Queue[j+1] = q.Queue[j+1], q.Queue[i+1]
	}
	q.mutex.Unlock()
}

// RandomNextTrack sets a random track as the next track to be played.
func (q *Queue) RandomNextTrack(queueWasEmpty bool) {
	q.mutex.Lock()
	if len(q.Queue) > 1 {
		nextTrackIndex := 1
		if queueWasEmpty {
			nextTrackIndex = 0
		}
		swapIndex := nextTrackIndex + rand.Intn(len(q.Queue)-1)
		q.Queue[nextTrackIndex], q.Queue[swapIndex] = q.Queue[swapIndex], q.Queue[nextTrackIndex]
	}
	q.mutex.Unlock()
}

// Skip performs the necessary actions that take place when a track is skipped
// via a command.
func (q *Queue) Skip() {
	// Stop audio stream if one exists.
	if DJ.AudioStream != nil {
		q.StopCurrent()
		DJ.AudioStream = nil
	}

	// Remove all track skips.
	DJ.Skips.ResetTrackSkips()

	q.mutex.Lock()
	// If caching is disabled, delete the track from disk.
	if !viper.GetBool("cache.enabled") {
		DJ.YouTubeDL.Delete(q.Queue[0])
	}

	// If automatic track shuffling is enabled, assign a random track in the queue to be the next track.
	if viper.GetBool("queue.automatic_shuffle_on") {
		q.mutex.Unlock()
		q.RandomNextTrack(false)
		q.mutex.Lock()
	}

	// Remove all playlist skips if this is the last track of the playlist still in the queue.
	if playlist := q.Queue[0].GetPlaylist(); playlist != nil {
		id := playlist.GetID()
		playlistIsFinished := true

		q.mutex.Unlock()
		q.Traverse(func(i int, t interfaces.Track) {
			if i != 0 && t.GetPlaylist() != nil {
				if t.GetPlaylist().GetID() == id {
					playlistIsFinished = false
				}
			}
		})
		q.mutex.Lock()

		if playlistIsFinished {
			DJ.Skips.ResetPlaylistSkips()
		}
	}

	// Skip the track.
	length := len(q.Queue)
	if length > 1 {
		q.Queue = q.Queue[1:]
	} else {
		q.Queue = make([]interfaces.Track, 0)
	}
	q.mutex.Unlock()

	if err := q.playIfNeeded(); err != nil {
		q.Skip()
	}
}

// SkipPlaylist performs the necessary actions that take place when a playlist
// is skipped via a command.
func (q *Queue) SkipPlaylist() {
	q.mutex.Lock()
	if playlist := q.Queue[0].GetPlaylist(); playlist != nil {
		currentPlaylistID := playlist.GetID()

		// We must loop backwards to prevent missing any elements after deletion.
		// NOTE: We do not remove the first track of the playlist quite yet as that
		// is removed properly with the following Skip() call.
		for i := len(q.Queue) - 1; i >= 1; i-- {
			if otherTrackPlaylist := q.Queue[i].GetPlaylist(); otherTrackPlaylist != nil {
				if otherTrackPlaylist.GetID() == currentPlaylistID {
					q.Queue = append(q.Queue[:i], q.Queue[i+1:]...)
				}
			}
		}
	}
	q.mutex.Unlock()
	q.StopCurrent()
}

// PlayCurrent creates a new audio stream and begins playing the current track.
func (q *Queue) PlayCurrent() error {
	currentTrack := q.GetTrack(0)
	filepath := os.ExpandEnv(viper.GetString("cache.directory") + "/" + currentTrack.GetFilename())
	if _, err := os.Stat(filepath); os.IsNotExist(err) {
		if err := DJ.YouTubeDL.Download(q.GetTrack(0)); err != nil {
			return err
		}
	}
	source := gumbleffmpeg.SourceFile(filepath)
	DJ.AudioStream = gumbleffmpeg.New(DJ.Client, source)
	DJ.AudioStream.Offset = currentTrack.GetPlaybackOffset()
	DJ.AudioStream.Volume = DJ.Volume

	if viper.GetString("defaults.player_command") == "avconv" {
		DJ.AudioStream.Command = "avconv"
	}

	if viper.GetBool("queue.announce_new_tracks") {
		message :=
			`

					%s (%s)

					Added by %s

			`
		message = fmt.Sprintf(message, currentTrack.GetThumbnailURL(), currentTrack.GetURL(),
			currentTrack.GetTitle(), currentTrack.GetDuration().String(), currentTrack.GetSubmitter())
		if currentTrack.GetPlaylist() != nil {
			message = fmt.Sprintf(message+`<tr><td align="center">From playlist "%s"</td></tr>`, currentTrack.GetPlaylist().GetTitle())
		}
		message += `</table>`
		DJ.Client.Self.Channel.Send(message, false)
	}

	DJ.AudioStream.Play()
	go func() {
		DJ.AudioStream.Wait()
		q.Skip()
	}()

	return nil
}

// PauseCurrent pauses the current audio stream if it exists and is not already paused.
func (q *Queue) PauseCurrent() error {
	if DJ.AudioStream == nil {
		return errors.New("There is no track to pause")
	}
	if DJ.AudioStream.State() == gumbleffmpeg.StatePaused {
		return errors.New("The track is already paused")
	}
	DJ.AudioStream.Pause()
	return nil
}

// ResumeCurrent resumes playback of the current audio stream if it exists and is paused.
func (q *Queue) ResumeCurrent() error {
	if DJ.AudioStream == nil {
		return errors.New("There is no track to resume")
	}
	if DJ.AudioStream.State() == gumbleffmpeg.StatePlaying {
		return errors.New("The track is already playing")
	}
	DJ.AudioStream.Play()
	return nil
}

// StopCurrent stops the playback of the current audio stream if it exists.
func (q *Queue) StopCurrent() error {
	if DJ.AudioStream == nil {
		return errors.New("The audio stream is nil")
	}
	DJ.AudioStream.Stop()
	DJ.AudioStream = nil
	return nil
}

func (q *Queue) playIfNeeded() error {
	if DJ.AudioStream == nil && q.Length() > 0 {
		if err := DJ.YouTubeDL.Download(q.GetTrack(0)); err != nil {
			return err
		}
		if err := q.PlayCurrent(); err != nil {
			return err
		}
	}
	return nil
}
<|endoftext|>


<commit_before>/******************************************************************************
 * A Discord API for Golang.
 *
 * Currently only the REST API is functional.  I will add on the websocket
 * layer once I get the API section where I want it.
 *
 * The idea is that this file is where we pull together the wsapi, and
 * restapi to create a single do-it-all struct
 *
 * NOTE!!! Currently this file has no purpose, it is here for future
 * access methods. EVERYTHING HERE will just go away or be changed
 * substantially in the future.
 */

package discordgo

// A Discord structure represents a all-inclusive (hopefully) structure to
// access the Discord REST API for a given authenticated user.
/*
type Discord struct {
	Session *Session
	User    User
	Servers []Server
}
*/
// New creates a new connection to Discord and returns a Discord structure.
// This provides an easy entry where most commonly needed information is
// automatically fetched.
// TODO add websocket code in here too
/*
func New(email string, password string) (d *Discord, err error) {

	session := Session{}

	session.Token, err = session.Login(email, password)
	if err != nil {
		return
	}

	user, err := session.Self()
	if err != nil {
		return
	}

	servers, err := session.Servers()

	d = &Discord{session, user, servers}

	return
}
*/
// Renew essentially reruns the New command without creating a new session.
// This will update all the user, server, and channel information that was
// fetched with the New command.  This is not an efficient way of doing this
// but if used infrequently it does provide convenience.
/*
func (d *Discord) Renew() (err error) {

	d.User, err = Users(&d.Session, "@me")
	d.Servers, err = Servers(&d.Session, "@me")

	return
}
*/
<commit_msg>discord.go file has no purpose right now.<commit_after><|endoftext|>


<commit_before>package client

import (
	"bytes"
	"text/template"
	"time"

	"github.com/goreleaser/goreleaser/context"
)

var (
	timeNow = time.Now
)

func releaseTitle(ctx *context.Context) (string, error) {
	var out bytes.Buffer
	t, err := template.New("github").
		Option("missingkey=error").
		Funcs(mkFuncMap()).
		Parse(ctx.Config.Release.NameTemplate)
	if err != nil {
		return "", err
	}
	err = t.Execute(&out, struct {
		ProjectName, Tag, Version string
	}{
		ProjectName: ctx.Config.ProjectName,
		Tag:         ctx.Git.CurrentTag,
		Version:     ctx.Version,
	})
	return out.String(), err
}

func mkFuncMap() template.FuncMap {
	return template.FuncMap{
		"time": func(s string) string {
			return timeNow().Format(s)
		},
	}
}
<commit_msg>style: simplified time template func<commit_after>package client

import (
	"bytes"
	"text/template"
	"time"

	"github.com/goreleaser/goreleaser/context"
)

func releaseTitle(ctx *context.Context) (string, error) {
	var out bytes.Buffer
	t, err := template.New("github").
		Option("missingkey=error").
		Funcs(mkFuncMap()).
		Parse(ctx.Config.Release.NameTemplate)
	if err != nil {
		return "", err
	}
	err = t.Execute(&out, struct {
		ProjectName, Tag, Version string
	}{
		ProjectName: ctx.Config.ProjectName,
		Tag:         ctx.Git.CurrentTag,
		Version:     ctx.Version,
	})
	return out.String(), err
}

func mkFuncMap() template.FuncMap {
	return template.FuncMap{
		"time": func(s string) string {
			return time.Now().Format(s)
		},
	}
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"

	"code.google.com/p/goauth2/oauth"
	"code.google.com/p/goauth2/oauth/jwt"
	"code.google.com/p/google-api-go-client/oauth2/v2"
	"google.golang.org/api/drive/v2"
)

var (
	uploadedFiles        = map[string]UploadFile{}
	GOOGLE_SERVICE_SCOPE = []string{"https://www.googleapis.com/auth/drive"}
	HISTORY_JSON         = ".history.json"
)

type UploadFile struct {
	Name         string                   `json:"name"`
	LastUpdateAt time.Time                `json:"last_update_at"`
	Folder       []*drive.ParentReference `json:"folder"`
	FileId       string                   `json:"file_id"`
}

type Uploader struct {
	config       *Config
	driveService *drive.Service
}

func NewUploader(config *Config) (*Uploader, error) {
	uploader := &Uploader{
		config: config,
	}

	return uploader, nil
}

func (uploader *Uploader) Check() error {
	for _, upload := range uploader.config.Uploads {
		_, err := os.Stat(upload.From)
		if err != nil {
			return err
		}

	}

	return nil
}

func (uploader *Uploader) Prepare() error {
	err := uploader.initGoogleService()
	if err != nil {
		return err
	}

	err = uploader.checkHistory()
	if err != nil {
		return err
	}

	return nil
}

func (uploader *Uploader) checkHistory() error {
	_, err := os.Stat(HISTORY_JSON)

	if err != nil && strings.Contains(err.Error(), "no such file or directory") {
		return nil
	} else if err != nil {
		return err
	}

	file, err := ioutil.ReadFile(HISTORY_JSON)
	if err != nil {
		return err
	}

	err = json.Unmarshal(file, &uploadedFiles)
	if err != nil {
		return err
	}

	return nil
}

func (uploader *Uploader) Run() error {
	for _, upload := range uploader.config.Uploads {
		file, err := os.Stat(upload.From)
		if err != nil {
			return err
		}

		if file.IsDir() {
			err = filepath.Walk(upload.From, func(path string, info os.FileInfo, err error) error {
				if err != nil {
					return err
				}
				if !info.IsDir() {
					media, err := os.Open(path)

					if err != nil {
						return err
					}

					defer media.Close()

					if uploadedFiles[media.Name()].Name == "" {
						uploadTo := strings.Replace(media.Name(), upload.From, upload.To, 1)
						result, err := uploader.uploadFile(media, uploadTo)
						if err != nil {
							writeJson()
							return err
						}

						uploadedFiles[media.Name()] = UploadFile{
							Name:         result.Title,
							LastUpdateAt: info.ModTime(),
							Folder:       result.Parents,
							FileId:       result.Id,
						}

					} else if uploadedFiles[media.Name()].Name != "" && uploadedFiles[media.Name()].LastUpdateAt != info.ModTime() {
						result, err := uploader.updateFile(media, uploadedFiles[media.Name()])
						if err != nil {
							writeJson()
							return err
						}

						uploadedFiles[media.Name()] = UploadFile{
							Name:         result.Title,
							LastUpdateAt: info.ModTime(),
							Folder:       result.Parents,
							FileId:       result.Id,
						}
					}

				}

				return nil
			})

			if err != nil {
				return err
			}
		} else {
			media, _ := os.Open(upload.From)
			defer media.Close()

			var result *drive.File
			if uploadedFiles[media.Name()].Name == "" {
				uploadTo := strings.Replace(media.Name(), upload.From, upload.To, 1)
				result, err = uploader.uploadFile(media, uploadTo)

				if err != nil {
					writeJson()
					return err
				}

			} else if uploadedFiles[media.Name()].LastUpdateAt != file.ModTime() {
				result, err = uploader.updateFile(media, uploadedFiles[media.Name()])

				if err != nil {
					writeJson()
					return err
				}
			}

			uploadedFiles[media.Name()] = UploadFile{
				Name:         result.Title,
				LastUpdateAt: file.ModTime(),
				Folder:       result.Parents,
				FileId:       result.Id,
			}
		}

		if err != nil {
			return err
		}
	}

	err := writeJson()
	if err != nil {
		return err
	}

	return nil
}

func (uploader *Uploader) initGoogleService() error {
	token := jwt.NewToken(
		uploader.config.ClientEmail,
		strings.Join(GOOGLE_SERVICE_SCOPE, " "),
		[]byte(uploader.config.PrivateKey),
	)

	client := &http.Client{}

	oauthToken, err := token.Assert(client)
	if err != nil {
		return err
	}

	transport := &oauth.Transport{
		Token: oauthToken,
	}

	c := transport.Client()

	_, err = oauth2.New(c)
	if err != nil {
		return err
	}

	driveService, err := drive.New(c)
	if err != nil {
		return err
	}

	uploader.driveService = driveService

	return nil
}

func (uploader *Uploader) createDir(name string, parentId string) (result *drive.File, err error) {
	parent := &drive.ParentReference{
		Id: parentId,
	}

	driveFile := &drive.File{
		Title:    name,
		Parents:  []*drive.ParentReference{parent},
		MimeType: "application/vnd.google-apps.folder",
	}

	result, err = uploader.driveService.Files.Insert(driveFile).Do()
	if err != nil {
		return nil, err
	}

	return result, nil
}

func (uploader *Uploader) uploadFile(media *os.File, path string) (result *drive.File, err error) {
	parentFolderId := uploader.config.Folder
	fileTitle := ""

	for _, folder := range strings.Split(strings.Replace(path, "//", "/", -1), "/") {
		list, _ := uploader.driveService.Files.List().Q(fmt.Sprintf("title='%s'", folder)).OrderBy("folder,createdDate").Do()
		if !strings.Contains(folder, ".") {
			items := map[string]string{}
			for _, item := range list.Items {
				items[item.Title] = item.Id
			}

			if len(list.Items) < 1 || items[folder] == "" {
				result, err := uploader.createDir(folder, parentFolderId)
				parentFolderId = result.Id

				if err != nil {
					return nil, err
				}
			} else {
				parentFolderId = items[folder]
			}
		} else {
			fileTitle = folder
		}
	}

	parent := &drive.ParentReference{
		Id: parentFolderId,
	}
	driveFile := &drive.File{
		Title:   fileTitle,
		Parents: []*drive.ParentReference{parent},
	}

	result, err = uploader.driveService.Files.Insert(driveFile).Media(media).Do()

	if err != nil {
		return nil, err
	}

	log.Println(strings.Replace(path, "//", "/", -1), "uploaded")

	return result, nil
}

func (uploader *Uploader) updateFile(media *os.File, file UploadFile) (result *drive.File, err error) {
	driveFile := &drive.File{
		Title: file.Name,
	}

	result, err = uploader.driveService.Files.Update(file.FileId, driveFile).Media(media).Do()
	if err != nil {
		return nil, err
	}

	log.Println(file.Name, " updated")

	return result, nil

}

func writeJson() error {
	j, err := json.Marshal(uploadedFiles)
	if err != nil {
		return err
	}

	ioutil.WriteFile(HISTORY_JSON, j, os.ModePerm)

	return nil
}
<commit_msg>use marshalindent<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"

	"code.google.com/p/goauth2/oauth"
	"code.google.com/p/goauth2/oauth/jwt"
	"code.google.com/p/google-api-go-client/oauth2/v2"
	"google.golang.org/api/drive/v2"
)

var (
	uploadedFiles        = map[string]UploadFile{}
	GOOGLE_SERVICE_SCOPE = []string{"https://www.googleapis.com/auth/drive"}
	HISTORY_JSON         = ".history.json"
)

type UploadFile struct {
	Name         string                   `json:"name"`
	LastUpdateAt time.Time                `json:"last_update_at"`
	Folder       []*drive.ParentReference `json:"folder"`
	FileId       string                   `json:"file_id"`
}

type Uploader struct {
	config       *Config
	driveService *drive.Service
}

func NewUploader(config *Config) (*Uploader, error) {
	uploader := &Uploader{
		config: config,
	}

	return uploader, nil
}

func (uploader *Uploader) Check() error {
	for _, upload := range uploader.config.Uploads {
		_, err := os.Stat(upload.From)
		if err != nil {
			return err
		}

	}

	return nil
}

func (uploader *Uploader) Prepare() error {
	err := uploader.initGoogleService()
	if err != nil {
		return err
	}

	err = uploader.checkHistory()
	if err != nil {
		return err
	}

	return nil
}

func (uploader *Uploader) checkHistory() error {
	_, err := os.Stat(HISTORY_JSON)

	if err != nil && strings.Contains(err.Error(), "no such file or directory") {
		return nil
	} else if err != nil {
		return err
	}

	file, err := ioutil.ReadFile(HISTORY_JSON)
	if err != nil {
		return err
	}

	err = json.Unmarshal(file, &uploadedFiles)
	if err != nil {
		return err
	}

	return nil
}

func (uploader *Uploader) Run() error {
	for _, upload := range uploader.config.Uploads {
		file, err := os.Stat(upload.From)
		if err != nil {
			return err
		}

		if file.IsDir() {
			err = filepath.Walk(upload.From, func(path string, info os.FileInfo, err error) error {
				if err != nil {
					return err
				}
				if !info.IsDir() {
					media, err := os.Open(path)

					if err != nil {
						return err
					}

					defer media.Close()

					if uploadedFiles[media.Name()].Name == "" {
						uploadTo := strings.Replace(media.Name(), upload.From, upload.To, 1)
						result, err := uploader.uploadFile(media, uploadTo)
						if err != nil {
							writeJson()
							return err
						}

						uploadedFiles[media.Name()] = UploadFile{
							Name:         result.Title,
							LastUpdateAt: info.ModTime(),
							Folder:       result.Parents,
							FileId:       result.Id,
						}

					} else if uploadedFiles[media.Name()].Name != "" && uploadedFiles[media.Name()].LastUpdateAt != info.ModTime() {
						result, err := uploader.updateFile(media, uploadedFiles[media.Name()])
						if err != nil {
							writeJson()
							return err
						}

						uploadedFiles[media.Name()] = UploadFile{
							Name:         result.Title,
							LastUpdateAt: info.ModTime(),
							Folder:       result.Parents,
							FileId:       result.Id,
						}
					}

				}

				return nil
			})

			if err != nil {
				return err
			}
		} else {
			media, _ := os.Open(upload.From)
			defer media.Close()

			var result *drive.File
			if uploadedFiles[media.Name()].Name == "" {
				uploadTo := strings.Replace(media.Name(), upload.From, upload.To, 1)
				result, err = uploader.uploadFile(media, uploadTo)

				if err != nil {
					writeJson()
					return err
				}

			} else if uploadedFiles[media.Name()].LastUpdateAt != file.ModTime() {
				result, err = uploader.updateFile(media, uploadedFiles[media.Name()])

				if err != nil {
					writeJson()
					return err
				}
			}

			uploadedFiles[media.Name()] = UploadFile{
				Name:         result.Title,
				LastUpdateAt: file.ModTime(),
				Folder:       result.Parents,
				FileId:       result.Id,
			}
		}

		if err != nil {
			return err
		}
	}

	err := writeJson()
	if err != nil {
		return err
	}

	return nil
}

func (uploader *Uploader) initGoogleService() error {
	token := jwt.NewToken(
		uploader.config.ClientEmail,
		strings.Join(GOOGLE_SERVICE_SCOPE, " "),
		[]byte(uploader.config.PrivateKey),
	)

	client := &http.Client{}

	oauthToken, err := token.Assert(client)
	if err != nil {
		return err
	}

	transport := &oauth.Transport{
		Token: oauthToken,
	}

	c := transport.Client()

	_, err = oauth2.New(c)
	if err != nil {
		return err
	}

	driveService, err := drive.New(c)
	if err != nil {
		return err
	}

	uploader.driveService = driveService

	return nil
}

func (uploader *Uploader) createDir(name string, parentId string) (result *drive.File, err error) {
	parent := &drive.ParentReference{
		Id: parentId,
	}

	driveFile := &drive.File{
		Title:    name,
		Parents:  []*drive.ParentReference{parent},
		MimeType: "application/vnd.google-apps.folder",
	}

	result, err = uploader.driveService.Files.Insert(driveFile).Do()
	if err != nil {
		return nil, err
	}

	return result, nil
}

func (uploader *Uploader) uploadFile(media *os.File, path string) (result *drive.File, err error) {
	parentFolderId := uploader.config.Folder
	fileTitle := ""

	for _, folder := range strings.Split(strings.Replace(path, "//", "/", -1), "/") {
		list, _ := uploader.driveService.Files.List().Q(fmt.Sprintf("title='%s'", folder)).OrderBy("folder,createdDate").Do()
		if !strings.Contains(folder, ".") {
			items := map[string]string{}
			for _, item := range list.Items {
				items[item.Title] = item.Id
			}

			if len(list.Items) < 1 || items[folder] == "" {
				result, err := uploader.createDir(folder, parentFolderId)
				parentFolderId = result.Id

				if err != nil {
					return nil, err
				}
			} else {
				parentFolderId = items[folder]
			}
		} else {
			fileTitle = folder
		}
	}

	parent := &drive.ParentReference{
		Id: parentFolderId,
	}
	driveFile := &drive.File{
		Title:   fileTitle,
		Parents: []*drive.ParentReference{parent},
	}

	result, err = uploader.driveService.Files.Insert(driveFile).Media(media).Do()

	if err != nil {
		return nil, err
	}

	log.Println(strings.Replace(path, "//", "/", -1), "uploaded")

	return result, nil
}

func (uploader *Uploader) updateFile(media *os.File, file UploadFile) (result *drive.File, err error) {
	driveFile := &drive.File{
		Title: file.Name,
	}

	result, err = uploader.driveService.Files.Update(file.FileId, driveFile).Media(media).Do()
	if err != nil {
		return nil, err
	}

	log.Println(file.Name, " updated")

	return result, nil

}

func writeJson() error {
	j, err := json.MarshalIndent(uploadedFiles, "", "  ")
	if err != nil {
		return err
	}

	ioutil.WriteFile(HISTORY_JSON, j, os.ModePerm)

	return nil
}
<|endoftext|>


<commit_before>package input

import (
	"fmt"
	"path"
	"path/filepath"
	"os"

	"github.com/cweill/gotests/internal/models"
)

// Returns all the Golang files for the given path. Ignores hidden files.
func Files(srcPath string) ([]models.Path, error) {
	srcPath, err := filepath.Abs(srcPath)
	if err != nil {
		return nil, fmt.Errorf("filepath.Abs: %v\n", err)
	}
	var fi os.FileInfo
	if fi, err = os.Stat(srcPath); err != nil {
		return nil, fmt.Errorf("os.Stat: %v\n", err)
	}
	if fi.IsDir() {
		return dirFiles(srcPath)
	}
	return file(srcPath)
}

func dirFiles(srcPath string) ([]models.Path, error) {
	ps, err := filepath.Glob(path.Join(srcPath, "*.go"))
	if err != nil {
		return nil, fmt.Errorf("filepath.Glob: %v\n", err)
	}
	var srcPaths []models.Path
	for _, p := range ps {
		src := models.Path(p)
		if isHiddenFile(p) || src.IsTestPath() {
			continue
		}
		srcPaths = append(srcPaths, src)
	}
	return srcPaths, nil
}

func file(srcPath string) ([]models.Path, error) {
	src := models.Path(srcPath)
	if filepath.Ext(srcPath) != ".go" || isHiddenFile(srcPath) {
		return nil, fmt.Errorf("no Go source files found at %v", srcPath)
	}
	return []models.Path{src}, nil
}

func isHiddenFile(path string) bool {
	return []rune(filepath.Base(path))[0] == '.'
}
<commit_msg>Fix function comments based on best practices from Effective Go (#89)<commit_after>package input

import (
	"fmt"
	"path"
	"path/filepath"
	"os"

	"github.com/cweill/gotests/internal/models"
)

// Files returns all the Golang files for the given path. Ignores hidden files.
func Files(srcPath string) ([]models.Path, error) {
	srcPath, err := filepath.Abs(srcPath)
	if err != nil {
		return nil, fmt.Errorf("filepath.Abs: %v\n", err)
	}
	var fi os.FileInfo
	if fi, err = os.Stat(srcPath); err != nil {
		return nil, fmt.Errorf("os.Stat: %v\n", err)
	}
	if fi.IsDir() {
		return dirFiles(srcPath)
	}
	return file(srcPath)
}

func dirFiles(srcPath string) ([]models.Path, error) {
	ps, err := filepath.Glob(path.Join(srcPath, "*.go"))
	if err != nil {
		return nil, fmt.Errorf("filepath.Glob: %v\n", err)
	}
	var srcPaths []models.Path
	for _, p := range ps {
		src := models.Path(p)
		if isHiddenFile(p) || src.IsTestPath() {
			continue
		}
		srcPaths = append(srcPaths, src)
	}
	return srcPaths, nil
}

func file(srcPath string) ([]models.Path, error) {
	src := models.Path(srcPath)
	if filepath.Ext(srcPath) != ".go" || isHiddenFile(srcPath) {
		return nil, fmt.Errorf("no Go source files found at %v", srcPath)
	}
	return []models.Path{src}, nil
}

func isHiddenFile(path string) bool {
	return []rune(filepath.Base(path))[0] == '.'
}
<|endoftext|>


<commit_before>package models

import (
	"log"

	"github.com/brnstz/bus/internal/etc"
	"github.com/brnstz/upsert"
	"github.com/jmoiron/sqlx"
)

type Trip struct {
	AgencyID string `json:"agency_id" db:"agency_id" upsert:"key"`
	RouteID  string `json:"route_id" db:"route_id" upsert:"key"`
	ID       string `json:"trip_id" db:"trip_id" upsert:"key"`

	ServiceID string `json:"service_id" db:"service_id"`
	ShapeID   string `json:"shape_id" db:"shape_id"`

	Headsign    string `json:"headsign" db:"headsign"`
	DirectionID int    `json:"direction_id" db:"direction_id"`

	ShapePoints []struct {
		Lat float64 `json:"lat"`
		Lon float64 `json:"lon"`
	} `json:"shape_points" db:"-" upsert:"omit"`
}

func NewTrip(id, routeID, agencyID, serviceID, shapeID, headsign string, direction int) (t *Trip, err error) {
	t = &Trip{
		ID:          id,
		AgencyID:    agencyID,
		RouteID:     routeID,
		ServiceID:   serviceID,
		ShapeID:     shapeID,
		Headsign:    headsign,
		DirectionID: direction,
	}

	return
}

func (t *Trip) Table() string {
	return "trip"
}

// Save saves a trip to the database
func (t *Trip) Save() error {
	_, err := upsert.Upsert(etc.DBConn, t)
	return err
}

func (t *Trip) addShapes(agencyID, shapeID string) (err error) {
	// Try to get the shapes specific to this trip
	q := `
		SELECT
			ST_X(location::geometry) AS lat,
			ST_Y(location::geometry) AS lon
		FROM shape
		WHERE agency_id = $1 AND
		      shape_id  = $2
		ORDER BY seq ASC
	`

	err = etc.DBConn.Select(&t.ShapePoints, q, agencyID, shapeID)
	if err != nil {
		log.Println("can't get shapes", err)
		return
	}

	return
}

func GetAnyTrip(agencyID, routeID string) (t Trip, err error) {
	var dummy int
	var shapeID string
	var tripID string

	// Get the most "popular" route
	q := `
		SELECT count(*) AS cnt, shape_id, trip_id
		FROM trip
		WHERE route_id = $1 AND char_length(shape_id) > 0
		GROUP BY shape_id, trip_id
		ORDER BY cnt DESC
		LIMIT 1
	`
	row := etc.DBConn.QueryRowx(q, routeID)
	err = row.Scan(&dummy, &shapeID, &tripID)
	if err != nil {
		log.Println("can't get shape_id", err)
		return
	}

	// Get the trip
	q = `
		SELECT *
		FROM trip
		WHERE agency_id	= $1 AND
		      trip_id = $2 AND
			  route_id = $3
	`

	err = etc.DBConn.Get(&t, q, agencyID, tripID, routeID)
	if err != nil {
		log.Println("can't get trip", q, agencyID, tripID, routeID, err)
		return
	}

	err = t.addShapes(agencyID, shapeID)
	if err != nil {
		log.Println("can't get shape", err)
		return
	}

	return
}

// GetTrip returns the trip for this agency and trip ID
func GetTrip(db sqlx.Ext, agencyID, routeID, tripID string) (t Trip, err error) {

	// Get the trip
	q := `
		SELECT *
		FROM trip
		WHERE agency_id	= $1 AND
		      trip_id = $2 AND
			  route_id = $3
	`

	err = sqlx.Get(db, &t, q, agencyID, tripID, routeID)
	if err != nil {
		//log.Println("can't get trip", q, agencyID, tripID, routeID, err)
		return
	} else {

		err = t.addShapes(agencyID, t.ShapeID)
		if err != nil {
			log.Println("can't get shapes", err)
			return
		}

		if len(t.ShapePoints) > 0 {
			return
		}
	}

	return
}
<commit_msg>simplify trip code<commit_after>package models

import (
	"log"

	"github.com/brnstz/bus/internal/etc"
	"github.com/brnstz/upsert"
	"github.com/jmoiron/sqlx"
)

type Trip struct {
	AgencyID string `json:"agency_id" db:"agency_id" upsert:"key"`
	RouteID  string `json:"route_id" db:"route_id" upsert:"key"`
	ID       string `json:"trip_id" db:"trip_id" upsert:"key"`

	ServiceID string `json:"service_id" db:"service_id"`
	ShapeID   string `json:"shape_id" db:"shape_id"`

	Headsign    string `json:"headsign" db:"headsign"`
	DirectionID int    `json:"direction_id" db:"direction_id"`

	ShapePoints []struct {
		Lat float64 `json:"lat"`
		Lon float64 `json:"lon"`
	} `json:"shape_points" db:"-" upsert:"omit"`
}

func NewTrip(id, routeID, agencyID, serviceID, shapeID, headsign string, direction int) (t *Trip, err error) {
	t = &Trip{
		ID:          id,
		AgencyID:    agencyID,
		RouteID:     routeID,
		ServiceID:   serviceID,
		ShapeID:     shapeID,
		Headsign:    headsign,
		DirectionID: direction,
	}

	return
}

func (t *Trip) Table() string {
	return "trip"
}

// Save saves a trip to the database
func (t *Trip) Save() error {
	_, err := upsert.Upsert(etc.DBConn, t)
	return err
}

func (t *Trip) addShapes(agencyID, shapeID string) (err error) {
	// Try to get the shapes specific to this trip
	q := `
		SELECT
			ST_X(location::geometry) AS lat,
			ST_Y(location::geometry) AS lon
		FROM shape
		WHERE agency_id = $1 AND
		      shape_id  = $2
		ORDER BY seq ASC
	`

	err = etc.DBConn.Select(&t.ShapePoints, q, agencyID, shapeID)
	if err != nil {
		log.Println("can't get shapes", err)
		return
	}

	return
}

// GetTrip returns the trip for this agency and trip ID
func GetTrip(db sqlx.Ext, agencyID, routeID, tripID string) (t Trip, err error) {

	// Get the trip
	q := `
		SELECT *
		FROM trip
		WHERE agency_id	= $1 AND
		      trip_id = $2 AND
			  route_id = $3
	`

	err = sqlx.Get(db, &t, q, agencyID, tripID, routeID)
	if err != nil {
		log.Println("can't get trip", q, agencyID, tripID, routeID, err)
		return
	}

	err = t.addShapes(agencyID, t.ShapeID)
	if err != nil {
		log.Println("can't get shapes", err)
		return
	}

	return
}
<|endoftext|>