<commit_before>package logdb

import (
	"encoding/binary"
	"io"
	"io/ioutil"
	"os"
	"sort"
	"strings"
	"sync"
)

// chunkSliceDB is the main LogDB instance, representing a database as
// a slice of memory-mapped files.
type chunkSliceDB struct {
	path string

	// Any number of goroutines can read simultaneously, but when
	// entries are added or removed, the write lock must be held.
	//
	// An alternative design point to consider is to have a
	// separate 'RWMutex' in each chunk, and hold only the
	// necessary write locks. This would complicate locking but
	// allow for more concurrent reading, and so may be better
	// under some work loads.
	rwlock sync.RWMutex

	chunkSize uint32

	chunks []*chunk

	oldest uint64

	newest uint64

	syncEvery     int
	sinceLastSync uint64
	syncDirty     []int
}

// A chunk is one memory-mapped file.
type chunk struct {
	path string

	bytes []byte
	mmapf *os.File

	// One past the ending addresses of entries in the 'bytes'
	// slice.
	//
	// This choice is because starting addresses can always be
	// calculated from ending addresses, as the first entry starts
	// at offset 0 (and there are no gaps). Ending addresses
	// cannot be calculated from starting addresses, unless the
	// ending address of the final entry is stored as well.
	ends []int32

	oldest uint64

	newest uint64
}

// Delete the files associated with a chunk.
func (c *chunk) closeAndRemove() error {
	if err := closeAndRemove(c.mmapf); err != nil {
		return err
	}
	return os.Remove(c.path + "-meta")
}

// fileInfoSlice implements nice sorting for 'os.FileInfo': first
// compare filenames by length, and then lexicographically.
type fileInfoSlice []os.FileInfo

func (fis fileInfoSlice) Len() int {
	return len(fis)
}

func (fis fileInfoSlice) Swap(i, j int) {
	fis[i], fis[j] = fis[j], fis[i]
}

func (fis fileInfoSlice) Less(i, j int) bool {
	ni := fis[i].Name()
	nj := fis[j].Name()
	return len(ni) < len(nj) || ni < nj
}

func createChunkSliceDB(path string, chunkSize uint32) (*chunkSliceDB, error) {
	// Write the "oldest" file.
	if err := writeFile(path+"/oldest", uint64(0)); err != nil {
		return nil, &WriteError{err}
	}

	return &chunkSliceDB{path: path, chunkSize: chunkSize, syncEvery: 100}, nil
}

func openChunkSliceDB(path string, chunkSize uint32) (*chunkSliceDB, error) {
	// Read the "oldest" file.
	var oldest uint64
	if err := readFile(path+"/oldest", &oldest); err != nil {
		return nil, &ReadError{err}
	}

	// Get all the chunk files.
	var chunkFiles []os.FileInfo
	fis, err := ioutil.ReadDir(path)
	if err != nil {
		return nil, &ReadError{err}
	}
	for _, fi := range fis {
		if !fi.IsDir() && strings.HasPrefix(fi.Name(), "chunk-") && !strings.HasSuffix(fi.Name(), "-meta") {
			chunkFiles = append(chunkFiles, fi)
		}
	}
	sort.Sort(fileInfoSlice(chunkFiles))

	// Populate the chunk slice.
	chunks := make([]*chunk, len(chunkFiles))
	chunkOldest := oldest
	chunkNewest := oldest
	done := false
	for i, fi := range chunkFiles {
		if done {
			// We ran out of strictly increasing ending
			// positions, which should ONLY happen in the
			// last chunk file.
			return nil, ErrCorrupt
		} else if i > 0 {
			chunkOldest = 1 + chunks[i-1].newest
		}

		var c chunk
		c, done, err = openChunkFile(fi, chunkOldest)
		if err != nil {
			return nil, err
		}
		chunks[i] = &c
		chunkNewest = chunkOldest + uint64(len(chunks[i].ends))
	}

	return &chunkSliceDB{
		path:      path,
		chunkSize: chunkSize,
		chunks:    chunks,
		oldest:    oldest,
		newest:    chunkNewest,
		syncEvery: 100,
	}, nil
}

// Open a chunk file
func openChunkFile(fi os.FileInfo, chunkOldest uint64) (chunk, bool, error) {
	chunk := chunk{
		path:   fi.Name(),
		oldest: chunkOldest,
	}

	// If 'done' gets set, then it means that chunk ending offsets
	// have stopped strictly increasing. This happens if entries
	// are rolled back, but not enough to delete an entire chunk:
	// the ending offsets get reset to 0 in the metadata file in
	// that case. This should only happen in the final chunk, so
	// it is an error for 'done' to become true if there are
	// further chunk files.
	var done bool

	// mmap the data file
	mmapf, bytes, err := mmap(fi.Name())
	if err != nil {
		return chunk, done, &ReadError{err}
	}
	chunk.bytes = bytes
	chunk.mmapf = mmapf

	// read the ending address metadata
	mfile, err := os.Open(fi.Name() + "-meta")
	if err != nil {
		return chunk, done, &ReadError{err}
	}
	prior := int32(-1)
	for {
		var this int32
		if err := binary.Read(mfile, binary.LittleEndian, &this); err != nil {
			if err == io.EOF {
				break
			}
			return chunk, done, ErrCorrupt
		}
		if this <= prior {
			done = true
			break
		}
		chunk.ends = append(chunk.ends, this)
		prior = this
	}

	chunk.newest = chunkOldest + uint64(len(chunk.ends))
	return chunk, done, nil
}

func (db *chunkSliceDB) Append(entry []byte) error {
	db.rwlock.Lock()
	defer db.rwlock.Unlock()

	panic("unimplemented")
}

func (db *chunkSliceDB) Get(id uint64) ([]byte, error) {
	db.rwlock.RLock()
	defer db.rwlock.RUnlock()

	// Check ID is in range.
	if id < db.oldest || id > db.newest || len(db.chunks) == 0 {
		return nil, ErrIDOutOfRange
	}

	// Binary search through chunks for the one containing the ID.
	lo := 0
	hi := len(db.chunks)
	mid := (hi - lo) / 2
	for ; !(db.chunks[mid].oldest <= id && id <= db.chunks[mid].newest); mid = (hi - lo) / 2 {
		if db.chunks[mid].newest < id {
			lo = mid
		} else if db.chunks[mid].oldest > id {
			hi = mid
		}
	}

	// Calculate the start and end offset, and return a copy of
	// the relevant byte slice.
	chunk := db.chunks[mid]
	off := id - chunk.oldest
	start := int32(0)
	if off > 0 {
		start = chunk.ends[off-1]
	}
	end := chunk.ends[off]
	out := make([]byte, end-start)
	for i := start; i < end; i++ {
		out[i] = chunk.bytes[start+i]
	}
	return out, nil
}

func (db *chunkSliceDB) Forget(newOldestID uint64) error {
	return db.truncate(newOldestID, db.newest)
}

func (db *chunkSliceDB) Rollback(newNewestID uint64) error {
	return db.truncate(db.oldest, newNewestID)
}

// Truncate the database at both ends.
func (db *chunkSliceDB) truncate(newOldestID uint64, newNewestID uint64) error {
	db.rwlock.Lock()
	defer db.rwlock.Unlock()

	if newOldestID < db.oldest || newNewestID > db.newest {
		return ErrIDOutOfRange
	}

	// Remove the metadata for any entries being rolled back.
	for i := len(db.chunks) - 1; i > 0 && db.chunks[i].newest > newNewestID; i-- {
		c := db.chunks[i]
		c.ends = c.ends[0 : c.newest-newNewestID]
	}

	db.sinceLastSync += newOldestID - db.oldest
	db.sinceLastSync += db.newest - newNewestID
	db.oldest = newOldestID
	db.newest = newNewestID

	// Check if this deleted any chunks
	first := 0
	last := len(db.chunks)
	for ; first < len(db.chunks) && db.chunks[first].newest < newOldestID; first++ {
	}
	for ; last > 0 && db.chunks[last].oldest > newNewestID; last-- {
	}

	if first > 0 || last < len(db.chunks) {
		// It did! Sync everything and then delete the files.
		if err := db.sync(false); err != nil {
			return err
		}
		for i, c := range db.chunks {
			if i >= first && i < last {
				continue
			}
			if err := c.closeAndRemove(); err != nil {
				return &DeleteError{err}
			}
		}
		db.chunks = db.chunks[first:last]
	}
	return db.periodicSync(false)
}

func (db *chunkSliceDB) Clone(path string, version uint16, chunkSize uint32) (LogDB, error) {
	db.rwlock.RLock()
	defer db.rwlock.RUnlock()

	panic("unimplemented")
}

func (db *chunkSliceDB) SetSync(every int) error {
	db.syncEvery = every

	// Immediately perform a periodic sync.
	return db.periodicSync(true)
}

func (db *chunkSliceDB) Sync() error {
	return db.sync(true)
}

// Perform a sync only if needed. This function is not safe to execute
// concurrently with a write, so the 'acquireLock' parameter MUST be
// true UNLESS the write lock is already held by this thread.
func (db *chunkSliceDB) periodicSync(acquireLock bool) error {
	// Sync if the number of unsynced entries is above the
	// threshold
	if db.syncEvery >= 0 && db.sinceLastSync > uint64(db.syncEvery) {
		return db.sync(acquireLock)
	}
	return nil
}

// This function is not safe to execute concurrently with a write, so
// the 'acquireLock' parameter MUST be true UNLESS the write lock is
// already held by this thread.
func (db *chunkSliceDB) sync(acquireLock bool) error {
	if acquireLock {
		db.rwlock.RLock()
		defer db.rwlock.RUnlock()
	}

	for _, i := range db.syncDirty {
		// To ensure ACID, sync the data first and only then
		// the metadata. This means that if there is a failure
		// between the two syncs, even if the newly-written
		// data is corrupt, there will be no metadata
		// referring to it, and so it will be invisible to the
		// database when next opened.
		if err := fsync(db.chunks[i].mmapf); err != nil {
			return &SyncError{err}
		}
		if err := writeFile(db.chunks[i].path+"-meta", db.chunks[i].ends); err != nil {
			return &SyncError{err}
		}
	}

	// Write the oldest entry ID.
	if err := writeFile(db.path+"/oldest", db.oldest); err != nil {
		return &SyncError{err}
	}

	db.syncDirty = nil
	db.sinceLastSync = 0

	return nil
}

func (db *chunkSliceDB) OldestID() uint64 {
	return db.oldest
}

func (db *chunkSliceDB) NewestID() uint64 {
	return db.newest
}
<commit_msg>Prrvent duplicate chunks in syncDirty<commit_after>package logdb

import (
	"encoding/binary"
	"io"
	"io/ioutil"
	"os"
	"sort"
	"strings"
	"sync"
)

// chunkSliceDB is the main LogDB instance, representing a database as
// a slice of memory-mapped files.
type chunkSliceDB struct {
	path string

	// Any number of goroutines can read simultaneously, but when
	// entries are added or removed, the write lock must be held.
	//
	// An alternative design point to consider is to have a
	// separate 'RWMutex' in each chunk, and hold only the
	// necessary write locks. This would complicate locking but
	// allow for more concurrent reading, and so may be better
	// under some work loads.
	rwlock sync.RWMutex

	chunkSize uint32

	chunks []*chunk

	oldest uint64

	newest uint64

	syncEvery     int
	sinceLastSync uint64
	syncDirty     []int
}

// A chunk is one memory-mapped file.
type chunk struct {
	path string

	bytes []byte
	mmapf *os.File

	// One past the ending addresses of entries in the 'bytes'
	// slice.
	//
	// This choice is because starting addresses can always be
	// calculated from ending addresses, as the first entry starts
	// at offset 0 (and there are no gaps). Ending addresses
	// cannot be calculated from starting addresses, unless the
	// ending address of the final entry is stored as well.
	ends []int32

	oldest uint64

	newest uint64

	dirty bool
}

// Delete the files associated with a chunk.
func (c *chunk) closeAndRemove() error {
	if err := closeAndRemove(c.mmapf); err != nil {
		return err
	}
	return os.Remove(c.path + "-meta")
}

// fileInfoSlice implements nice sorting for 'os.FileInfo': first
// compare filenames by length, and then lexicographically.
type fileInfoSlice []os.FileInfo

func (fis fileInfoSlice) Len() int {
	return len(fis)
}

func (fis fileInfoSlice) Swap(i, j int) {
	fis[i], fis[j] = fis[j], fis[i]
}

func (fis fileInfoSlice) Less(i, j int) bool {
	ni := fis[i].Name()
	nj := fis[j].Name()
	return len(ni) < len(nj) || ni < nj
}

func createChunkSliceDB(path string, chunkSize uint32) (*chunkSliceDB, error) {
	// Write the "oldest" file.
	if err := writeFile(path+"/oldest", uint64(0)); err != nil {
		return nil, &WriteError{err}
	}

	return &chunkSliceDB{path: path, chunkSize: chunkSize, syncEvery: 100}, nil
}

func openChunkSliceDB(path string, chunkSize uint32) (*chunkSliceDB, error) {
	// Read the "oldest" file.
	var oldest uint64
	if err := readFile(path+"/oldest", &oldest); err != nil {
		return nil, &ReadError{err}
	}

	// Get all the chunk files.
	var chunkFiles []os.FileInfo
	fis, err := ioutil.ReadDir(path)
	if err != nil {
		return nil, &ReadError{err}
	}
	for _, fi := range fis {
		if !fi.IsDir() && strings.HasPrefix(fi.Name(), "chunk-") && !strings.HasSuffix(fi.Name(), "-meta") {
			chunkFiles = append(chunkFiles, fi)
		}
	}
	sort.Sort(fileInfoSlice(chunkFiles))

	// Populate the chunk slice.
	chunks := make([]*chunk, len(chunkFiles))
	chunkOldest := oldest
	chunkNewest := oldest
	done := false
	for i, fi := range chunkFiles {
		if done {
			// We ran out of strictly increasing ending
			// positions, which should ONLY happen in the
			// last chunk file.
			return nil, ErrCorrupt
		} else if i > 0 {
			chunkOldest = 1 + chunks[i-1].newest
		}

		var c chunk
		c, done, err = openChunkFile(fi, chunkOldest)
		if err != nil {
			return nil, err
		}
		chunks[i] = &c
		chunkNewest = chunkOldest + uint64(len(chunks[i].ends))
	}

	return &chunkSliceDB{
		path:      path,
		chunkSize: chunkSize,
		chunks:    chunks,
		oldest:    oldest,
		newest:    chunkNewest,
		syncEvery: 100,
	}, nil
}

// Open a chunk file
func openChunkFile(fi os.FileInfo, chunkOldest uint64) (chunk, bool, error) {
	chunk := chunk{
		path:   fi.Name(),
		oldest: chunkOldest,
	}

	// If 'done' gets set, then it means that chunk ending offsets
	// have stopped strictly increasing. This happens if entries
	// are rolled back, but not enough to delete an entire chunk:
	// the ending offsets get reset to 0 in the metadata file in
	// that case. This should only happen in the final chunk, so
	// it is an error for 'done' to become true if there are
	// further chunk files.
	var done bool

	// mmap the data file
	mmapf, bytes, err := mmap(fi.Name())
	if err != nil {
		return chunk, done, &ReadError{err}
	}
	chunk.bytes = bytes
	chunk.mmapf = mmapf

	// read the ending address metadata
	mfile, err := os.Open(fi.Name() + "-meta")
	if err != nil {
		return chunk, done, &ReadError{err}
	}
	prior := int32(-1)
	for {
		var this int32
		if err := binary.Read(mfile, binary.LittleEndian, &this); err != nil {
			if err == io.EOF {
				break
			}
			return chunk, done, ErrCorrupt
		}
		if this <= prior {
			done = true
			break
		}
		chunk.ends = append(chunk.ends, this)
		prior = this
	}

	chunk.newest = chunkOldest + uint64(len(chunk.ends))
	return chunk, done, nil
}

func (db *chunkSliceDB) Append(entry []byte) error {
	db.rwlock.Lock()
	defer db.rwlock.Unlock()

	panic("unimplemented")
}

func (db *chunkSliceDB) Get(id uint64) ([]byte, error) {
	db.rwlock.RLock()
	defer db.rwlock.RUnlock()

	// Check ID is in range.
	if id < db.oldest || id > db.newest || len(db.chunks) == 0 {
		return nil, ErrIDOutOfRange
	}

	// Binary search through chunks for the one containing the ID.
	lo := 0
	hi := len(db.chunks)
	mid := (hi - lo) / 2
	for ; !(db.chunks[mid].oldest <= id && id <= db.chunks[mid].newest); mid = (hi - lo) / 2 {
		if db.chunks[mid].newest < id {
			lo = mid
		} else if db.chunks[mid].oldest > id {
			hi = mid
		}
	}

	// Calculate the start and end offset, and return a copy of
	// the relevant byte slice.
	chunk := db.chunks[mid]
	off := id - chunk.oldest
	start := int32(0)
	if off > 0 {
		start = chunk.ends[off-1]
	}
	end := chunk.ends[off]
	out := make([]byte, end-start)
	for i := start; i < end; i++ {
		out[i] = chunk.bytes[start+i]
	}
	return out, nil
}

func (db *chunkSliceDB) Forget(newOldestID uint64) error {
	return db.truncate(newOldestID, db.newest)
}

func (db *chunkSliceDB) Rollback(newNewestID uint64) error {
	return db.truncate(db.oldest, newNewestID)
}

// Truncate the database at both ends.
func (db *chunkSliceDB) truncate(newOldestID uint64, newNewestID uint64) error {
	db.rwlock.Lock()
	defer db.rwlock.Unlock()

	if newOldestID < db.oldest || newNewestID > db.newest {
		return ErrIDOutOfRange
	}

	// Remove the metadata for any entries being rolled back.
	for i := len(db.chunks) - 1; i > 0 && db.chunks[i].newest > newNewestID; i-- {
		c := db.chunks[i]
		c.ends = c.ends[0 : c.newest-newNewestID]
		if !c.dirty {
			c.dirty = true
			db.syncDirty = append(db.syncDirty, i)
		}
	}

	db.sinceLastSync += newOldestID - db.oldest
	db.sinceLastSync += db.newest - newNewestID
	db.oldest = newOldestID
	db.newest = newNewestID

	// Check if this deleted any chunks
	first := 0
	last := len(db.chunks)
	for ; first < len(db.chunks) && db.chunks[first].newest < newOldestID; first++ {
	}
	for ; last > 0 && db.chunks[last].oldest > newNewestID; last-- {
	}

	if first > 0 || last < len(db.chunks) {
		// It did! Sync everything and then delete the files.
		if err := db.sync(false); err != nil {
			return err
		}
		for i, c := range db.chunks {
			if i >= first && i < last {
				continue
			}
			if err := c.closeAndRemove(); err != nil {
				return &DeleteError{err}
			}
		}
		db.chunks = db.chunks[first:last]
	}
	return db.periodicSync(false)
}

func (db *chunkSliceDB) Clone(path string, version uint16, chunkSize uint32) (LogDB, error) {
	db.rwlock.RLock()
	defer db.rwlock.RUnlock()

	panic("unimplemented")
}

func (db *chunkSliceDB) SetSync(every int) error {
	db.syncEvery = every

	// Immediately perform a periodic sync.
	return db.periodicSync(true)
}

func (db *chunkSliceDB) Sync() error {
	return db.sync(true)
}

// Perform a sync only if needed. This function is not safe to execute
// concurrently with a write, so the 'acquireLock' parameter MUST be
// true UNLESS the write lock is already held by this thread.
func (db *chunkSliceDB) periodicSync(acquireLock bool) error {
	// Sync if the number of unsynced entries is above the
	// threshold
	if db.syncEvery >= 0 && db.sinceLastSync > uint64(db.syncEvery) {
		return db.sync(acquireLock)
	}
	return nil
}

// This function is not safe to execute concurrently with a write, so
// the 'acquireLock' parameter MUST be true UNLESS the write lock is
// already held by this thread.
func (db *chunkSliceDB) sync(acquireLock bool) error {
	if acquireLock {
		db.rwlock.RLock()
		defer db.rwlock.RUnlock()
	}

	for _, i := range db.syncDirty {
		// To ensure ACID, sync the data first and only then
		// the metadata. This means that if there is a failure
		// between the two syncs, even if the newly-written
		// data is corrupt, there will be no metadata
		// referring to it, and so it will be invisible to the
		// database when next opened.
		if err := fsync(db.chunks[i].mmapf); err != nil {
			return &SyncError{err}
		}
		if err := writeFile(db.chunks[i].path+"-meta", db.chunks[i].ends); err != nil {
			return &SyncError{err}
		}
		db.chunks[i].dirty = false
	}

	// Write the oldest entry ID.
	if err := writeFile(db.path+"/oldest", db.oldest); err != nil {
		return &SyncError{err}
	}

	db.syncDirty = nil
	db.sinceLastSync = 0

	return nil
}

func (db *chunkSliceDB) OldestID() uint64 {
	return db.oldest
}

func (db *chunkSliceDB) NewestID() uint64 {
	return db.newest
}
<|endoftext|>


<commit_before>package dockertest

import (
	"fmt"
	"os"
	"runtime"
	"time"

	"strings"

	"github.com/cenk/backoff"
	dc "github.com/fsouza/go-dockerclient"
	"github.com/pkg/errors"
)

// Pool represents a connection to the docker API and is used to create and remove docker images.
type Pool struct {
	Client  *dc.Client
	MaxWait time.Duration
}

// Resource represents a docker container.
type Resource struct {
	Container *dc.Container
}

// GetPort returns a resource's published port. You can use it to connect to the service via localhost, e.g. tcp://localhost:1231/
func (r *Resource) GetPort(id string) string {
	if r.Container == nil {
		return ""
	} else if r.Container.NetworkSettings == nil {
		return ""
	}

	m, ok := r.Container.NetworkSettings.Ports[dc.Port(id)]
	if !ok {
		return ""
	} else if len(m) == 0 {
		return ""
	}

	return m[0].HostPort
}

// NewTLSPool creates a new pool given an endpoint and the certificate path. This is required for endpoints that
// require TLS communication.
func NewTLSPool(endpoint, certpath string) (*Pool, error) {
	ca := fmt.Sprintf("%s/ca.pem", certpath)
	cert := fmt.Sprintf("%s/cert.pem", certpath)
	key := fmt.Sprintf("%s/key.pem", certpath)

	client, err := dc.NewTLSClient(endpoint, cert, key, ca)
	if err != nil {
		return nil, errors.Wrap(err, "")
	}

	return &Pool{
		Client: client,
	}, nil
}

// NewPool creates a new pool. You can pass an empty string to use the default, which is taken from the environment
// variable DOCKER_URL, or from docker-machine if the environment variable DOCKER_MACHINE_NAME is set,
// or if neither is defined a sensible default for the operating system you are on.
func NewPool(endpoint string) (*Pool, error) {
	if endpoint == "" {
		if os.Getenv("DOCKER_URL") != "" {
			endpoint = os.Getenv("DOCKER_URL")
		} else if os.Getenv("DOCKER_MACHINE_NAME") != "" {
			client, err := dc.NewClientFromEnv()
			if err != nil {
				return nil, errors.Wrap(err, "")
			}

			return &Pool{Client: client}, nil
		} else if runtime.GOOS == "windows" {
			endpoint = "http://localhost:2375"
		} else {
			endpoint = "unix:///var/run/docker.sock"
		}
	}

	client, err := dc.NewClient(endpoint)
	if err != nil {
		return nil, errors.Wrap(err, "")
	}

	return &Pool{
		Client: client,
	}, nil
}

// RunOptions is used to pass in optional parameters when running a container.
type RunOptions struct {
	Repository string
	Tag        string
	Env        []string
	Cmd        []string
	Mounts     []string
}

// RunWithOptions starts a docker container.
//
// pool.Run(&RunOptions{Repository: "mongo", Cmd: []string{"mongod", "--smallfiles"}})
func (d *Pool) RunWithOptions(opts *RunOptions) (*Resource, error) {
	repository := opts.Repository
	tag := opts.Tag
	env := opts.Env
	cmd := opts.Cmd

	mounts := []dc.Mount{}

	for _, m := range opts.Mounts {
		sd := strings.Split(m, ":")
		if len(sd) == 2 {
			mounts = append(mounts, dc.Mount{
				Source:      sd[0],
				Destination: sd[1],
				RW:          true,
			})
		} else {
			return nil, errors.Wrap(fmt.Errorf("invalid mount format: got %s, expected <src>:<dst>", m), "")
		}
	}

	if tag == "" {
		tag = "latest"
	}

	_, err := d.Client.InspectImage(fmt.Sprintf("%s:%s", repository, tag))
	if err != nil {
		if err := d.Client.PullImage(dc.PullImageOptions{
			Repository: repository,
			Tag:        tag,
		}, dc.AuthConfiguration{}); err != nil {
			return nil, errors.Wrap(err, "")
		}
	}

	c, err := d.Client.CreateContainer(dc.CreateContainerOptions{
		Config: &dc.Config{
			Image:  fmt.Sprintf("%s:%s", repository, tag),
			Env:    env,
			Cmd:    cmd,
			Mounts: mounts,
		},
		HostConfig: &dc.HostConfig{
			PublishAllPorts: true,
			Binds:           opts.Mounts,
		},
	})
	if err != nil {
		return nil, errors.Wrap(err, "")
	}

	if err := d.Client.StartContainer(c.ID, nil); err != nil {
		return nil, errors.Wrap(err, "")
	}

	c, err = d.Client.InspectContainer(c.ID)
	if err != nil {
		return nil, errors.Wrap(err, "")
	}

	return &Resource{
		Container: c,
	}, nil
}

// Run starts a docker container.
//
// pool.Run("mysql", "5.3", []string{"FOO=BAR", "BAR=BAZ"})
func (d *Pool) Run(repository, tag string, env []string) (*Resource, error) {
	return d.RunWithOptions(&RunOptions{Repository: repository, Tag: tag, Env: env})
}

// Purge removes a container and linked volumes from docker.
func (d *Pool) Purge(r *Resource) error {
	if err := d.Client.KillContainer(dc.KillContainerOptions{ID: r.Container.ID}); err != nil {
		return errors.Wrap(err, "")
	}

	if err := d.Client.RemoveContainer(dc.RemoveContainerOptions{ID: r.Container.ID, Force: true, RemoveVolumes: true}); err != nil {
		return errors.Wrap(err, "")
	}

	return nil
}

// Retry is an exponential backoff retry helper. You can use it to wait for e.g. mysql to boot up.
func (d *Pool) Retry(op func() error) error {
	if d.MaxWait == 0 {
		d.MaxWait = time.Minute
	}
	bo := backoff.NewExponentialBackOff()
	bo.MaxInterval = time.Second * 5
	bo.MaxElapsedTime = d.MaxWait
	return backoff.Retry(op, bo)
}
<commit_msg>core: expose links to config (#90)<commit_after>package dockertest

import (
	"fmt"
	"os"
	"runtime"
	"time"

	"strings"

	"github.com/cenk/backoff"
	dc "github.com/fsouza/go-dockerclient"
	"github.com/pkg/errors"
)

// Pool represents a connection to the docker API and is used to create and remove docker images.
type Pool struct {
	Client  *dc.Client
	MaxWait time.Duration
}

// Resource represents a docker container.
type Resource struct {
	Container *dc.Container
}

// GetPort returns a resource's published port. You can use it to connect to the service via localhost, e.g. tcp://localhost:1231/
func (r *Resource) GetPort(id string) string {
	if r.Container == nil {
		return ""
	} else if r.Container.NetworkSettings == nil {
		return ""
	}

	m, ok := r.Container.NetworkSettings.Ports[dc.Port(id)]
	if !ok {
		return ""
	} else if len(m) == 0 {
		return ""
	}

	return m[0].HostPort
}

// NewTLSPool creates a new pool given an endpoint and the certificate path. This is required for endpoints that
// require TLS communication.
func NewTLSPool(endpoint, certpath string) (*Pool, error) {
	ca := fmt.Sprintf("%s/ca.pem", certpath)
	cert := fmt.Sprintf("%s/cert.pem", certpath)
	key := fmt.Sprintf("%s/key.pem", certpath)

	client, err := dc.NewTLSClient(endpoint, cert, key, ca)
	if err != nil {
		return nil, errors.Wrap(err, "")
	}

	return &Pool{
		Client: client,
	}, nil
}

// NewPool creates a new pool. You can pass an empty string to use the default, which is taken from the environment
// variable DOCKER_URL, or from docker-machine if the environment variable DOCKER_MACHINE_NAME is set,
// or if neither is defined a sensible default for the operating system you are on.
func NewPool(endpoint string) (*Pool, error) {
	if endpoint == "" {
		if os.Getenv("DOCKER_URL") != "" {
			endpoint = os.Getenv("DOCKER_URL")
		} else if os.Getenv("DOCKER_MACHINE_NAME") != "" {
			client, err := dc.NewClientFromEnv()
			if err != nil {
				return nil, errors.Wrap(err, "")
			}

			return &Pool{Client: client}, nil
		} else if runtime.GOOS == "windows" {
			endpoint = "http://localhost:2375"
		} else {
			endpoint = "unix:///var/run/docker.sock"
		}
	}

	client, err := dc.NewClient(endpoint)
	if err != nil {
		return nil, errors.Wrap(err, "")
	}

	return &Pool{
		Client: client,
	}, nil
}

// RunOptions is used to pass in optional parameters when running a container.
type RunOptions struct {
	Repository string
	Tag        string
	Env        []string
	Cmd        []string
	Mounts     []string
	Links      []string
}

// RunWithOptions starts a docker container.
//
// pool.Run(&RunOptions{Repository: "mongo", Cmd: []string{"mongod", "--smallfiles"}})
func (d *Pool) RunWithOptions(opts *RunOptions) (*Resource, error) {
	repository := opts.Repository
	tag := opts.Tag
	env := opts.Env
	cmd := opts.Cmd

	mounts := []dc.Mount{}

	for _, m := range opts.Mounts {
		sd := strings.Split(m, ":")
		if len(sd) == 2 {
			mounts = append(mounts, dc.Mount{
				Source:      sd[0],
				Destination: sd[1],
				RW:          true,
			})
		} else {
			return nil, errors.Wrap(fmt.Errorf("invalid mount format: got %s, expected <src>:<dst>", m), "")
		}
	}

	if tag == "" {
		tag = "latest"
	}

	_, err := d.Client.InspectImage(fmt.Sprintf("%s:%s", repository, tag))
	if err != nil {
		if err := d.Client.PullImage(dc.PullImageOptions{
			Repository: repository,
			Tag:        tag,
		}, dc.AuthConfiguration{}); err != nil {
			return nil, errors.Wrap(err, "")
		}
	}

	c, err := d.Client.CreateContainer(dc.CreateContainerOptions{
		Config: &dc.Config{
			Image:  fmt.Sprintf("%s:%s", repository, tag),
			Env:    env,
			Cmd:    cmd,
			Mounts: mounts,
		},
		HostConfig: &dc.HostConfig{
			PublishAllPorts: true,
			Binds:           opts.Mounts,
			Links:           opts.Links,
		},
	})
	if err != nil {
		return nil, errors.Wrap(err, "")
	}

	if err := d.Client.StartContainer(c.ID, nil); err != nil {
		return nil, errors.Wrap(err, "")
	}

	c, err = d.Client.InspectContainer(c.ID)
	if err != nil {
		return nil, errors.Wrap(err, "")
	}

	return &Resource{
		Container: c,
	}, nil
}

// Run starts a docker container.
//
// pool.Run("mysql", "5.3", []string{"FOO=BAR", "BAR=BAZ"})
func (d *Pool) Run(repository, tag string, env []string) (*Resource, error) {
	return d.RunWithOptions(&RunOptions{Repository: repository, Tag: tag, Env: env})
}

// Purge removes a container and linked volumes from docker.
func (d *Pool) Purge(r *Resource) error {
	if err := d.Client.KillContainer(dc.KillContainerOptions{ID: r.Container.ID}); err != nil {
		return errors.Wrap(err, "")
	}

	if err := d.Client.RemoveContainer(dc.RemoveContainerOptions{ID: r.Container.ID, Force: true, RemoveVolumes: true}); err != nil {
		return errors.Wrap(err, "")
	}

	return nil
}

// Retry is an exponential backoff retry helper. You can use it to wait for e.g. mysql to boot up.
func (d *Pool) Retry(op func() error) error {
	if d.MaxWait == 0 {
		d.MaxWait = time.Minute
	}
	bo := backoff.NewExponentialBackOff()
	bo.MaxInterval = time.Second * 5
	bo.MaxElapsedTime = d.MaxWait
	return backoff.Retry(op, bo)
}
<|endoftext|>


<commit_before><commit_msg>remove example, dont need it<commit_after><|endoftext|>


<commit_before><commit_msg>lxd/apparmor/archive: Expand all paths<commit_after><|endoftext|>


<commit_before>package tuikit

import (
	"fmt"

	"github.com/nsf/tulib"
)

type BaseView struct {
	lastPaintedRect    Rect
	paintSubscriber    func()
	resizeSubscriber   func()
	updateChildrenRect func(Rect) error
	childrenRect       map[Painter]Rect
	childrenNeedPaint  map[Painter]bool
	childrenNeedResize bool
}

func NewBaseView() *BaseView {
	return &BaseView{
		childrenRect:      make(map[Painter]Rect),
		childrenNeedPaint: make(map[Painter]bool),
	}
}

func (v *BaseView) NeedPaint() {
	if v.paintSubscriber != nil {
		v.paintSubscriber()
	}
}

func (v *BaseView) NeedResize() {
	if v.resizeSubscriber != nil {
		v.resizeSubscriber()
	}
}

func (v *BaseView) AttachChild(child Painter, rect Rect) {
	if r, ok := v.childrenRect[child]; ok {
		if r.Eq(rect) {
			return
		}
	}

	child.SetPaintSubscriber(func() { v.ChildNeedsPaint(child) })
	child.SetResizeSubscriber(func() { v.childrenNeedResize = true })
	v.childrenRect[child] = rect
	v.childrenNeedPaint[child] = true
}

func (v *BaseView) DetachChild(child Painter) {
	child.SetPaintSubscriber(nil)
	delete(v.childrenRect, child)
	delete(v.childrenNeedPaint, child)
}

func (v *BaseView) ChildNeedsPaint(child Painter) {
	v.childrenNeedPaint[child] = true
	v.NeedPaint()
}

func (v *BaseView) SetUpdateChildrenRect(cb func(Rect) error) {
	v.updateChildrenRect = cb
}

//----------------------------------------------------------------------------
// Painter Interface
//----------------------------------------------------------------------------

// BaseView.PaintTo does not paint anything itself, but it delegates painting
// to its children
func (v *BaseView) PaintTo(buffer *tulib.Buffer, rect Rect) error {
	if !v.lastPaintedRect.Eq(rect) || v.childrenNeedResize {
		if err := v.updateChildrenRect(rect); err != nil {
			return fmt.Errorf("Error when updateChildrenRect: %v", err)
		}
	}

	for c, r := range v.childrenRect {
		if !v.childrenNeedPaint[c] {
			continue
		}
		if err := c.PaintTo(buffer, r.Intersect(rect)); err != nil {
			return fmt.Errorf("Error when paintTo: %v", err)
		}
		delete(v.childrenNeedPaint, c)
	}

	v.lastPaintedRect = rect
	return nil
}

func (v *BaseView) DesiredMinSize() Size {
	return NewSize(1, 1)
}

func (v *BaseView) SetPaintSubscriber(cb func()) {
	v.paintSubscriber = cb
}

func (v *BaseView) SetResizeSubscriber(cb func()) {
	v.resizeSubscriber = cb
}
<commit_msg>Guard from NPE on updateChildrenRect<commit_after>package tuikit

import (
	"fmt"

	"github.com/nsf/tulib"
)

type BaseView struct {
	lastPaintedRect    Rect
	paintSubscriber    func()
	resizeSubscriber   func()
	updateChildrenRect func(Rect) error
	childrenRect       map[Painter]Rect
	childrenNeedPaint  map[Painter]bool
	childrenNeedResize bool
}

func NewBaseView() *BaseView {
	return &BaseView{
		childrenRect:      make(map[Painter]Rect),
		childrenNeedPaint: make(map[Painter]bool),
	}
}

func (v *BaseView) NeedPaint() {
	if v.paintSubscriber != nil {
		v.paintSubscriber()
	}
}

func (v *BaseView) NeedResize() {
	if v.resizeSubscriber != nil {
		v.resizeSubscriber()
	}
}

func (v *BaseView) AttachChild(child Painter, rect Rect) {
	if r, ok := v.childrenRect[child]; ok {
		if r.Eq(rect) {
			return
		}
	}

	child.SetPaintSubscriber(func() { v.ChildNeedsPaint(child) })
	child.SetResizeSubscriber(func() { v.childrenNeedResize = true })
	v.childrenRect[child] = rect
	v.childrenNeedPaint[child] = true
}

func (v *BaseView) DetachChild(child Painter) {
	child.SetPaintSubscriber(nil)
	delete(v.childrenRect, child)
	delete(v.childrenNeedPaint, child)
}

func (v *BaseView) ChildNeedsPaint(child Painter) {
	v.childrenNeedPaint[child] = true
	v.NeedPaint()
}

func (v *BaseView) SetUpdateChildrenRect(cb func(Rect) error) {
	v.updateChildrenRect = cb
}

//----------------------------------------------------------------------------
// Painter Interface
//----------------------------------------------------------------------------

// BaseView.PaintTo does not paint anything itself, but it delegates painting
// to its children
func (v *BaseView) PaintTo(buffer *tulib.Buffer, rect Rect) error {
	if !v.lastPaintedRect.Eq(rect) || v.childrenNeedResize {
		if v.updateChildrenRect == nil {
			return fmt.Errorf("v.updateChildrenRect is null")
		}

		if err := v.updateChildrenRect(rect); err != nil {
			return fmt.Errorf("Error when updateChildrenRect: %v", err)
		}
	}

	for c, r := range v.childrenRect {
		if !v.childrenNeedPaint[c] {
			continue
		}
		if err := c.PaintTo(buffer, r.Intersect(rect)); err != nil {
			return fmt.Errorf("Error when paintTo: %v", err)
		}
		delete(v.childrenNeedPaint, c)
	}

	v.lastPaintedRect = rect
	return nil
}

func (v *BaseView) DesiredMinSize() Size {
	return NewSize(1, 1)
}

func (v *BaseView) SetPaintSubscriber(cb func()) {
	v.paintSubscriber = cb
}

func (v *BaseView) SetResizeSubscriber(cb func()) {
	v.resizeSubscriber = cb
}
<|endoftext|>


<commit_before>package twitter

import (
	"fmt"
	"net/http"

	"github.com/dghubble/sling"
)

// Tweet represents a Twitter Tweet, previously called a status.
// https://dev.twitter.com/overview/api/tweets
// Unused or deprecated fields not provided: Geo, Annotations
type Tweet struct {
	Contributors         []Contributor          `json:"contributors"`
	ContributorNumber    int64                  `json:"contributors"`
	Coordinates          *Coordinates           `json:"coordinates"`
	CreatedAt            string                 `json:"created_at"`
	CurrentUserRetweet   *TweetIdentifier       `json:"current_user_retweet"`
	Entities             *Entities              `json:"entities"`
	FavoriteCount        int                    `json:"favorite_count"`
	Favorited            bool                   `json:"favorited"`
	FilterLevel          string                 `json:"filter_level"`
	ID                   int64                  `json:"id"`
	IDStr                string                 `json:"id_str"`
	InReplyToScreenName  string                 `json:"in_reply_to_screen_name"`
	InReplyToStatusID    int64                  `json:"in_reply_to_status_id"`
	InReplyToStatusIDStr string                 `json:"in_reply_to_status_id_str"`
	InReplyToUserID      int64                  `json:"in_reply_to_user_id"`
	InReplyToUserIDStr   string                 `json:"in_reply_to_user_id_str"`
	Lang                 string                 `json:"lang"`
	PossiblySensitive    bool                   `json:"possibly_sensitive"`
	RetweetCount         int                    `json:"retweet_count"`
	Retweeted            bool                   `json:"retweeted"`
	RetweetedStatus      *Tweet                 `json:"retweeted_status"`
	Source               string                 `json:"source"`
	Scopes               map[string]interface{} `json:"scopes"`
	Text                 string                 `json:"text"`
	Place                *Place                 `json:"place"`
	Truncated            bool                   `json:"truncated"`
	User                 *User                  `json:"user"`
	WithheldCopyright    bool                   `json:"withheld_copyright"`
	WithheldInCountries  []string               `json:"withheld_in_countries"`
	WithheldScope        string                 `json:"withheld_scope"`
	ExtendedEntities     *ExtendedEntity        `json:"extended_entities"`
	QuotedStatusID       int64                  `json:"quoted_status_id"`
	QuotedStatusIDStr    string                 `json:"quoted_status_id_str"`
	QuotedStatus         *Tweet                 `json:"quoted_status"`
}

// Place represents a Twitter Place / Location
// https://dev.twitter.com/overview/api/places
type Place struct {
	Attributes  map[string]string `json:"attributes"`
	BoundingBox *BoundingBox      `json:"bounding_box"`
	Country     string            `json:"country"`
	CountryCode string            `json:"country_code"`
	FullName    string            `json:"full_name"`
	Geometry    *BoundingBox      `json:"geometry"`
	ID          string            `json:"id"`
	Name        string            `json:"name"`
	PlaceType   string            `json:"place_type"`
	Polylines   []string          `json:"polylines"`
	URL         string            `json:"url"`
}

// BoundingBox represents the bounding coordinates (longitude, latitutde)
// defining the bounds of a box containing a Place entity.
type BoundingBox struct {
	Coordinates [][][2]float64 `json:"coordinates"`
	Type        string         `json:"type"`
}

// Contributor represents a brief summary of a User identifiers.
type Contributor struct {
	ID         int64  `json:"id"`
	IDStr      string `json:"id_str"`
	ScreenName string `json:"screen_name"`
}

// Coordinates are pairs of longitude and latitude locations.
type Coordinates struct {
	Coordinates [2]float64 `json:"coordinates"`
	Type        string     `json:"type"`
}

// TweetIdentifier represents the id by which a Tweet can be identified.
type TweetIdentifier struct {
	ID    int64  `json:"id"`
	IDStr string `json:"id_str"`
}

// StatusService provides methods for accessing Twitter status API endpoints.
type StatusService struct {
	sling *sling.Sling
}

// newStatusService returns a new StatusService.
func newStatusService(sling *sling.Sling) *StatusService {
	return &StatusService{
		sling: sling.Path("statuses/"),
	}
}

// StatusShowParams are the parameters for StatusService.Show
type StatusShowParams struct {
	ID               int64 `url:"id,omitempty"`
	TrimUser         *bool `url:"trim_user,omitempty"`
	IncludeMyRetweet *bool `url:"include_my_retweet,omitempty"`
	IncludeEntities  *bool `url:"include_entities,omitempty"`
}

// Show returns the requested Tweet.
// https://dev.twitter.com/rest/reference/get/statuses/show/%3Aid
func (s *StatusService) Show(id int64, params *StatusShowParams) (*Tweet, *http.Response, error) {
	if params == nil {
		params = &StatusShowParams{}
	}
	params.ID = id
	tweet := new(Tweet)
	apiError := new(APIError)
	resp, err := s.sling.New().Get("show.json").QueryStruct(params).Receive(tweet, apiError)
	return tweet, resp, relevantError(err, *apiError)
}

// StatusLookupParams are the parameters for StatusService.Lookup
type StatusLookupParams struct {
	ID              []int64 `url:"id,omitempty,comma"`
	TrimUser        *bool   `url:"trim_user,omitempty"`
	IncludeEntities *bool   `url:"include_entities,omitempty"`
	Map             *bool   `url:"map,omitempty"`
}

// Lookup returns the requested Tweets as a slice. Combines ids from the
// required ids argument and from params.Id.
// https://dev.twitter.com/rest/reference/get/statuses/lookup
func (s *StatusService) Lookup(ids []int64, params *StatusLookupParams) ([]Tweet, *http.Response, error) {
	if params == nil {
		params = &StatusLookupParams{}
	}
	params.ID = append(params.ID, ids...)
	tweets := new([]Tweet)
	apiError := new(APIError)
	resp, err := s.sling.New().Get("lookup.json").QueryStruct(params).Receive(tweets, apiError)
	return *tweets, resp, relevantError(err, *apiError)
}

// StatusUpdateParams are the parameters for StatusService.Update
type StatusUpdateParams struct {
	Status             string   `url:"status,omitempty"`
	InReplyToStatusID  int64    `url:"in_reply_to_status_id,omitempty"`
	PossiblySensitive  *bool    `url:"possibly_sensitive,omitempty"`
	Lat                *float64 `url:"lat,omitempty"`
	Long               *float64 `url:"long,omitempty"`
	PlaceID            string   `url:"place_id,omitempty"`
	DisplayCoordinates *bool    `url:"display_coordinates,omitempty"`
	TrimUser           *bool    `url:"trim_user,omitempty"`
	MediaIds           []int64  `url:"media_ids,omitempty,comma"`
}

// Update updates the user's status, also known as Tweeting.
// Requires a user auth context.
// https://dev.twitter.com/rest/reference/post/statuses/update
func (s *StatusService) Update(status string, params *StatusUpdateParams) (*Tweet, *http.Response, error) {
	if params == nil {
		params = &StatusUpdateParams{}
	}
	params.Status = status
	tweet := new(Tweet)
	apiError := new(APIError)
	resp, err := s.sling.New().Post("update.json").BodyForm(params).Receive(tweet, apiError)
	return tweet, resp, relevantError(err, *apiError)
}

// StatusRetweetParams are the parameters for StatusService.Retweet
type StatusRetweetParams struct {
	ID       int64 `url:"id,omitempty"`
	TrimUser *bool `url:"trim_user,omitempty"`
}

// Retweet retweets the Tweet with the given id and returns the original Tweet
// with embedded retweet details.
// Requires a user auth context.
// https://dev.twitter.com/rest/reference/post/statuses/retweet/%3Aid
func (s *StatusService) Retweet(id int64, params *StatusRetweetParams) (*Tweet, *http.Response, error) {
	if params == nil {
		params = &StatusRetweetParams{}
	}
	params.ID = id
	tweet := new(Tweet)
	apiError := new(APIError)
	path := fmt.Sprintf("retweet/%d.json", params.ID)
	resp, err := s.sling.New().Post(path).BodyForm(params).Receive(tweet, apiError)
	return tweet, resp, relevantError(err, *apiError)
}

// UnRetweet untweets a retweeted status. Returns the original Tweet with
// retweet details embedded.
// https://dev.twitter.com/rest/reference/post/statuses/unretweet/%3Aid
func (s *StatusService) UnRetweet(id int64, params *StatusRetweetParams) (*Tweet, *http.Response, error) {
	if params == nil {
		params = &StatusRetweetParams{}
	}
	params.ID = id
	tweet := new(Tweet)
	apiError := new(APIError)
	path := fmt.Sprintf("unretweet/%d.json", params.ID)
	resp, err := s.sling.New().Post(path).BodyForm(params).Receive(tweet, apiError)
	return tweet, resp, relevantError(err, *apiError)
}

// StatusRetweetsParams are the parameters for StatusService.Retweets
type StatusRetweetsParams struct {
	ID       int64 `url:"id,omitempty"`
	Count    int   `url:"count,omitempty"`
	TrimUser *bool `url:"trim_user,omitempty"`
}

// Retweets returns the most recent retweets of the Tweet with the given id.
// https://dev.twitter.com/rest/reference/get/statuses/retweets/%3Aid
func (s *StatusService) Retweets(id int64, params *StatusRetweetsParams) ([]Tweet, *http.Response, error) {
	if params == nil {
		params = &StatusRetweetsParams{}
	}
	params.ID = id
	tweets := new([]Tweet)
	apiError := new(APIError)
	path := fmt.Sprintf("retweets/%d.json", params.ID)
	resp, err := s.sling.New().Get(path).QueryStruct(params).Receive(tweets, apiError)
	return *tweets, resp, relevantError(err, *apiError)
}

// StatusRetweetsOfMeParams are the parameters for StatusService.RetweetsOfMe
type StatusRetweetsOfMeParams struct {
	Count               int64 `url:"count,omitempty"`
	SinceID             int64 `url:"since_id,omitempty"`
	MaxID               int64 `url:"max_id,omitempty"`
	TrimUser            bool  `url:"trim_user,omitempty"`
	IncludeEntities     bool  `url:"include_entities,omitempty"`
	IncludeUserEntities bool  `url:"include_user_entities,omitempty"`
}

// RetweetsOfMe returns the most recent tweets authored by the authenticating
// user that have been retweeted by others.
// https://dev.twitter.com/rest/reference/get/statuses/retweets_of_me
func (s *StatusService) RetweetsOfMe(params *StatusRetweetsOfMeParams) ([]Tweet, *http.Response, error) {
	if params == nil {
		params = &StatusRetweetsOfMeParams{}
	}
	tweets := new([]Tweet)
	apiError := new(APIError)
	resp, err := s.sling.New().Get("retweets_of_me.json").QueryStruct(params).Receive(tweets, apiError)
	return *tweets, resp, relevantError(err, *apiError)
}

// StatusRetweetersResult is the result from StatusService.Retweeters
type StatusRetweetersResult struct {
	PreviousCursor    int64   `json:"previous_cursor"`
	PreviousCursorStr string  `json:"previous_cursor_str"`
	NextCursor        int64   `json:"next_cursor"`
	NextCursorStr     string  `json:"next_cursor_str"`
	IDs               []int64 `json:"ids"`
}

// StatusRetweetersParams are the parameters for StatusService.Retweeters
type StatusRetweetersParams struct {
	ID           int64 `url:"id"`
	Cursor       int64 `url:"cursor,omitempty"`
	StringifyIDs bool  `url:"stringify_ids,omitempty"`
}

// Retweeters returns a collection of up to 100 user IDs belonging to users
// who have retweeted the tweet specified by the id parameter
// https://dev.twitter.com/rest/reference/get/statuses/retweeters/ids
func (s *StatusService) Retweeters(params *StatusRetweetersParams) (StatusRetweetersResult, *http.Response, error) {
	tweets := new(StatusRetweetersResult)
	apiError := new(APIError)
	resp, err := s.sling.New().Get("retweeters/ids.json").QueryStruct(params).Receive(tweets, apiError)
	return *tweets, resp, relevantError(err, *apiError)
}

// StatusDestroyParams are the parameters for StatusService.Destroy
type StatusDestroyParams struct {
	ID       int64 `url:"id,omitempty"`
	TrimUser *bool `url:"trim_user,omitempty"`
}

// Destroy deletes the Tweet with the given id and returns it if successful.
// Requires a user auth context.
// https://dev.twitter.com/rest/reference/post/statuses/destroy/%3Aid
func (s *StatusService) Destroy(id int64, params *StatusDestroyParams) (*Tweet, *http.Response, error) {
	if params == nil {
		params = &StatusDestroyParams{}
	}
	params.ID = id
	tweet := new(Tweet)
	apiError := new(APIError)
	path := fmt.Sprintf("destroy/%d.json", params.ID)
	resp, err := s.sling.New().Post(path).BodyForm(params).Receive(tweet, apiError)
	return tweet, resp, relevantError(err, *apiError)
}

// OEmbedTweet represents a Tweet in oEmbed format.
type OEmbedTweet struct {
	URL          string `json:"url"`
	ProviderURL  string `json:"provider_url"`
	ProviderName string `json:"provider_name"`
	AuthorName   string `json:"author_name"`
	Version      string `json:"version"`
	AuthorURL    string `json:"author_url"`
	Type         string `json:"type"`
	HTML         string `json:"html"`
	Height       int64  `json:"height"`
	Width        int64  `json:"width"`
	CacheAge     string `json:"cache_age"`
}

// StatusOEmbedParams are the parameters for StatusService.OEmbed
type StatusOEmbedParams struct {
	ID         int64  `url:"id,omitempty"`
	URL        string `url:"url,omitempty"`
	Align      string `url:"align,omitempty"`
	MaxWidth   int64  `url:"maxwidth,omitempty"`
	HideMedia  *bool  `url:"hide_media,omitempty"`
	HideThread *bool  `url:"hide_media,omitempty"`
	OmitScript *bool  `url:"hide_media,omitempty"`
	WidgetType string `url:"widget_type,omitempty"`
	HideTweet  *bool  `url:"hide_tweet,omitempty"`
}

// OEmbed returns the requested Tweet in oEmbed format.
// https://dev.twitter.com/rest/reference/get/statuses/oembed
func (s *StatusService) OEmbed(params *StatusOEmbedParams) (*OEmbedTweet, *http.Response, error) {
	oEmbedTweet := new(OEmbedTweet)
	apiError := new(APIError)
	resp, err := s.sling.New().Get("oembed.json").QueryStruct(params).Receive(oEmbedTweet, apiError)
	return oEmbedTweet, resp, relevantError(err, *apiError)
}
<commit_msg>Eliminate duplicate contributors<commit_after>package twitter

import (
	"fmt"
	"net/http"

	"github.com/dghubble/sling"
)

// Tweet represents a Twitter Tweet, previously called a status.
// https://dev.twitter.com/overview/api/tweets
// Unused or deprecated fields not provided: Geo, Annotations
type Tweet struct {
	Contributors         []Contributor          `json:"contributors"`
	Coordinates          *Coordinates           `json:"coordinates"`
	CreatedAt            string                 `json:"created_at"`
	CurrentUserRetweet   *TweetIdentifier       `json:"current_user_retweet"`
	Entities             *Entities              `json:"entities"`
	FavoriteCount        int                    `json:"favorite_count"`
	Favorited            bool                   `json:"favorited"`
	FilterLevel          string                 `json:"filter_level"`
	ID                   int64                  `json:"id"`
	IDStr                string                 `json:"id_str"`
	InReplyToScreenName  string                 `json:"in_reply_to_screen_name"`
	InReplyToStatusID    int64                  `json:"in_reply_to_status_id"`
	InReplyToStatusIDStr string                 `json:"in_reply_to_status_id_str"`
	InReplyToUserID      int64                  `json:"in_reply_to_user_id"`
	InReplyToUserIDStr   string                 `json:"in_reply_to_user_id_str"`
	Lang                 string                 `json:"lang"`
	PossiblySensitive    bool                   `json:"possibly_sensitive"`
	RetweetCount         int                    `json:"retweet_count"`
	Retweeted            bool                   `json:"retweeted"`
	RetweetedStatus      *Tweet                 `json:"retweeted_status"`
	Source               string                 `json:"source"`
	Scopes               map[string]interface{} `json:"scopes"`
	Text                 string                 `json:"text"`
	Place                *Place                 `json:"place"`
	Truncated            bool                   `json:"truncated"`
	User                 *User                  `json:"user"`
	WithheldCopyright    bool                   `json:"withheld_copyright"`
	WithheldInCountries  []string               `json:"withheld_in_countries"`
	WithheldScope        string                 `json:"withheld_scope"`
	ExtendedEntities     *ExtendedEntity        `json:"extended_entities"`
	QuotedStatusID       int64                  `json:"quoted_status_id"`
	QuotedStatusIDStr    string                 `json:"quoted_status_id_str"`
	QuotedStatus         *Tweet                 `json:"quoted_status"`
}

// Place represents a Twitter Place / Location
// https://dev.twitter.com/overview/api/places
type Place struct {
	Attributes  map[string]string `json:"attributes"`
	BoundingBox *BoundingBox      `json:"bounding_box"`
	Country     string            `json:"country"`
	CountryCode string            `json:"country_code"`
	FullName    string            `json:"full_name"`
	Geometry    *BoundingBox      `json:"geometry"`
	ID          string            `json:"id"`
	Name        string            `json:"name"`
	PlaceType   string            `json:"place_type"`
	Polylines   []string          `json:"polylines"`
	URL         string            `json:"url"`
}

// BoundingBox represents the bounding coordinates (longitude, latitutde)
// defining the bounds of a box containing a Place entity.
type BoundingBox struct {
	Coordinates [][][2]float64 `json:"coordinates"`
	Type        string         `json:"type"`
}

// Contributor represents a brief summary of a User identifiers.
type Contributor struct {
	ID         int64  `json:"id"`
	IDStr      string `json:"id_str"`
	ScreenName string `json:"screen_name"`
}

// Coordinates are pairs of longitude and latitude locations.
type Coordinates struct {
	Coordinates [2]float64 `json:"coordinates"`
	Type        string     `json:"type"`
}

// TweetIdentifier represents the id by which a Tweet can be identified.
type TweetIdentifier struct {
	ID    int64  `json:"id"`
	IDStr string `json:"id_str"`
}

// StatusService provides methods for accessing Twitter status API endpoints.
type StatusService struct {
	sling *sling.Sling
}

// newStatusService returns a new StatusService.
func newStatusService(sling *sling.Sling) *StatusService {
	return &StatusService{
		sling: sling.Path("statuses/"),
	}
}

// StatusShowParams are the parameters for StatusService.Show
type StatusShowParams struct {
	ID               int64 `url:"id,omitempty"`
	TrimUser         *bool `url:"trim_user,omitempty"`
	IncludeMyRetweet *bool `url:"include_my_retweet,omitempty"`
	IncludeEntities  *bool `url:"include_entities,omitempty"`
}

// Show returns the requested Tweet.
// https://dev.twitter.com/rest/reference/get/statuses/show/%3Aid
func (s *StatusService) Show(id int64, params *StatusShowParams) (*Tweet, *http.Response, error) {
	if params == nil {
		params = &StatusShowParams{}
	}
	params.ID = id
	tweet := new(Tweet)
	apiError := new(APIError)
	resp, err := s.sling.New().Get("show.json").QueryStruct(params).Receive(tweet, apiError)
	return tweet, resp, relevantError(err, *apiError)
}

// StatusLookupParams are the parameters for StatusService.Lookup
type StatusLookupParams struct {
	ID              []int64 `url:"id,omitempty,comma"`
	TrimUser        *bool   `url:"trim_user,omitempty"`
	IncludeEntities *bool   `url:"include_entities,omitempty"`
	Map             *bool   `url:"map,omitempty"`
}

// Lookup returns the requested Tweets as a slice. Combines ids from the
// required ids argument and from params.Id.
// https://dev.twitter.com/rest/reference/get/statuses/lookup
func (s *StatusService) Lookup(ids []int64, params *StatusLookupParams) ([]Tweet, *http.Response, error) {
	if params == nil {
		params = &StatusLookupParams{}
	}
	params.ID = append(params.ID, ids...)
	tweets := new([]Tweet)
	apiError := new(APIError)
	resp, err := s.sling.New().Get("lookup.json").QueryStruct(params).Receive(tweets, apiError)
	return *tweets, resp, relevantError(err, *apiError)
}

// StatusUpdateParams are the parameters for StatusService.Update
type StatusUpdateParams struct {
	Status             string   `url:"status,omitempty"`
	InReplyToStatusID  int64    `url:"in_reply_to_status_id,omitempty"`
	PossiblySensitive  *bool    `url:"possibly_sensitive,omitempty"`
	Lat                *float64 `url:"lat,omitempty"`
	Long               *float64 `url:"long,omitempty"`
	PlaceID            string   `url:"place_id,omitempty"`
	DisplayCoordinates *bool    `url:"display_coordinates,omitempty"`
	TrimUser           *bool    `url:"trim_user,omitempty"`
	MediaIds           []int64  `url:"media_ids,omitempty,comma"`
}

// Update updates the user's status, also known as Tweeting.
// Requires a user auth context.
// https://dev.twitter.com/rest/reference/post/statuses/update
func (s *StatusService) Update(status string, params *StatusUpdateParams) (*Tweet, *http.Response, error) {
	if params == nil {
		params = &StatusUpdateParams{}
	}
	params.Status = status
	tweet := new(Tweet)
	apiError := new(APIError)
	resp, err := s.sling.New().Post("update.json").BodyForm(params).Receive(tweet, apiError)
	return tweet, resp, relevantError(err, *apiError)
}

// StatusRetweetParams are the parameters for StatusService.Retweet
type StatusRetweetParams struct {
	ID       int64 `url:"id,omitempty"`
	TrimUser *bool `url:"trim_user,omitempty"`
}

// Retweet retweets the Tweet with the given id and returns the original Tweet
// with embedded retweet details.
// Requires a user auth context.
// https://dev.twitter.com/rest/reference/post/statuses/retweet/%3Aid
func (s *StatusService) Retweet(id int64, params *StatusRetweetParams) (*Tweet, *http.Response, error) {
	if params == nil {
		params = &StatusRetweetParams{}
	}
	params.ID = id
	tweet := new(Tweet)
	apiError := new(APIError)
	path := fmt.Sprintf("retweet/%d.json", params.ID)
	resp, err := s.sling.New().Post(path).BodyForm(params).Receive(tweet, apiError)
	return tweet, resp, relevantError(err, *apiError)
}

// UnRetweet untweets a retweeted status. Returns the original Tweet with
// retweet details embedded.
// https://dev.twitter.com/rest/reference/post/statuses/unretweet/%3Aid
func (s *StatusService) UnRetweet(id int64, params *StatusRetweetParams) (*Tweet, *http.Response, error) {
	if params == nil {
		params = &StatusRetweetParams{}
	}
	params.ID = id
	tweet := new(Tweet)
	apiError := new(APIError)
	path := fmt.Sprintf("unretweet/%d.json", params.ID)
	resp, err := s.sling.New().Post(path).BodyForm(params).Receive(tweet, apiError)
	return tweet, resp, relevantError(err, *apiError)
}

// StatusRetweetsParams are the parameters for StatusService.Retweets
type StatusRetweetsParams struct {
	ID       int64 `url:"id,omitempty"`
	Count    int   `url:"count,omitempty"`
	TrimUser *bool `url:"trim_user,omitempty"`
}

// Retweets returns the most recent retweets of the Tweet with the given id.
// https://dev.twitter.com/rest/reference/get/statuses/retweets/%3Aid
func (s *StatusService) Retweets(id int64, params *StatusRetweetsParams) ([]Tweet, *http.Response, error) {
	if params == nil {
		params = &StatusRetweetsParams{}
	}
	params.ID = id
	tweets := new([]Tweet)
	apiError := new(APIError)
	path := fmt.Sprintf("retweets/%d.json", params.ID)
	resp, err := s.sling.New().Get(path).QueryStruct(params).Receive(tweets, apiError)
	return *tweets, resp, relevantError(err, *apiError)
}

// StatusRetweetsOfMeParams are the parameters for StatusService.RetweetsOfMe
type StatusRetweetsOfMeParams struct {
	Count               int64 `url:"count,omitempty"`
	SinceID             int64 `url:"since_id,omitempty"`
	MaxID               int64 `url:"max_id,omitempty"`
	TrimUser            bool  `url:"trim_user,omitempty"`
	IncludeEntities     bool  `url:"include_entities,omitempty"`
	IncludeUserEntities bool  `url:"include_user_entities,omitempty"`
}

// RetweetsOfMe returns the most recent tweets authored by the authenticating
// user that have been retweeted by others.
// https://dev.twitter.com/rest/reference/get/statuses/retweets_of_me
func (s *StatusService) RetweetsOfMe(params *StatusRetweetsOfMeParams) ([]Tweet, *http.Response, error) {
	if params == nil {
		params = &StatusRetweetsOfMeParams{}
	}
	tweets := new([]Tweet)
	apiError := new(APIError)
	resp, err := s.sling.New().Get("retweets_of_me.json").QueryStruct(params).Receive(tweets, apiError)
	return *tweets, resp, relevantError(err, *apiError)
}

// StatusRetweetersResult is the result from StatusService.Retweeters
type StatusRetweetersResult struct {
	PreviousCursor    int64   `json:"previous_cursor"`
	PreviousCursorStr string  `json:"previous_cursor_str"`
	NextCursor        int64   `json:"next_cursor"`
	NextCursorStr     string  `json:"next_cursor_str"`
	IDs               []int64 `json:"ids"`
}

// StatusRetweetersParams are the parameters for StatusService.Retweeters
type StatusRetweetersParams struct {
	ID           int64 `url:"id"`
	Cursor       int64 `url:"cursor,omitempty"`
	StringifyIDs bool  `url:"stringify_ids,omitempty"`
}

// Retweeters returns a collection of up to 100 user IDs belonging to users
// who have retweeted the tweet specified by the id parameter
// https://dev.twitter.com/rest/reference/get/statuses/retweeters/ids
func (s *StatusService) Retweeters(params *StatusRetweetersParams) (StatusRetweetersResult, *http.Response, error) {
	tweets := new(StatusRetweetersResult)
	apiError := new(APIError)
	resp, err := s.sling.New().Get("retweeters/ids.json").QueryStruct(params).Receive(tweets, apiError)
	return *tweets, resp, relevantError(err, *apiError)
}

// StatusDestroyParams are the parameters for StatusService.Destroy
type StatusDestroyParams struct {
	ID       int64 `url:"id,omitempty"`
	TrimUser *bool `url:"trim_user,omitempty"`
}

// Destroy deletes the Tweet with the given id and returns it if successful.
// Requires a user auth context.
// https://dev.twitter.com/rest/reference/post/statuses/destroy/%3Aid
func (s *StatusService) Destroy(id int64, params *StatusDestroyParams) (*Tweet, *http.Response, error) {
	if params == nil {
		params = &StatusDestroyParams{}
	}
	params.ID = id
	tweet := new(Tweet)
	apiError := new(APIError)
	path := fmt.Sprintf("destroy/%d.json", params.ID)
	resp, err := s.sling.New().Post(path).BodyForm(params).Receive(tweet, apiError)
	return tweet, resp, relevantError(err, *apiError)
}

// OEmbedTweet represents a Tweet in oEmbed format.
type OEmbedTweet struct {
	URL          string `json:"url"`
	ProviderURL  string `json:"provider_url"`
	ProviderName string `json:"provider_name"`
	AuthorName   string `json:"author_name"`
	Version      string `json:"version"`
	AuthorURL    string `json:"author_url"`
	Type         string `json:"type"`
	HTML         string `json:"html"`
	Height       int64  `json:"height"`
	Width        int64  `json:"width"`
	CacheAge     string `json:"cache_age"`
}

// StatusOEmbedParams are the parameters for StatusService.OEmbed
type StatusOEmbedParams struct {
	ID         int64  `url:"id,omitempty"`
	URL        string `url:"url,omitempty"`
	Align      string `url:"align,omitempty"`
	MaxWidth   int64  `url:"maxwidth,omitempty"`
	HideMedia  *bool  `url:"hide_media,omitempty"`
	HideThread *bool  `url:"hide_media,omitempty"`
	OmitScript *bool  `url:"hide_media,omitempty"`
	WidgetType string `url:"widget_type,omitempty"`
	HideTweet  *bool  `url:"hide_tweet,omitempty"`
}

// OEmbed returns the requested Tweet in oEmbed format.
// https://dev.twitter.com/rest/reference/get/statuses/oembed
func (s *StatusService) OEmbed(params *StatusOEmbedParams) (*OEmbedTweet, *http.Response, error) {
	oEmbedTweet := new(OEmbedTweet)
	apiError := new(APIError)
	resp, err := s.sling.New().Get("oembed.json").QueryStruct(params).Receive(oEmbedTweet, apiError)
	return oEmbedTweet, resp, relevantError(err, *apiError)
}
<|endoftext|>


<commit_before>package projects

import "time"

const (
	// GetSoftwareListEndpoint is the path to the endpoint for retrieving a specific Software List
	GetSoftwareListEndpoint = "v1/project/getSBOM"
	// GetSoftwareListsEndpoint is the path to the endpoint for retrieving an organization's Software Lists
	GetSoftwareListsEndpoint = "v1/project/getSBOMs"
)

// SoftwareList represents a software list containing zero or more Component objects.
type SoftwareList struct {
	ID               string      `json:"id"`
	Name             string      `json:"name"`
	Version          string      `json:"version"`
	Supplier         string      `json:"supplier"`
	ContactName      string      `json:"contact_name"`
	ContactEmail     string      `json:"contact_email"`
	MonitorFrequency string      `json:"monitor_frequency"`
	Status           string      `json:"status"`
	CreatedAt        time.Time   `json:"created_at"`
	UpdatedAt        time.Time   `json:"updated_at"`
	DeletedAt        *time.Time  `json:"deleted_at"`
	EntryCount       int         `json:"entry_count"`
	Stats            Stats       `json:"stats"`
	Entries          []Component `json:"entries"`
	TeamID           string      `json:"team_id"`
	OrgID            string      `json:"org_id"`
	RulesetID        string      `json:"ruleset_id"`
}

type Risk struct {
	Score  int `json:"score"`
	Scopes struct {
		Ecosystem   int `json:"ecosystem"`
		SupplyChain int `json:"supplyChain"`
		Software    int `json:"software"`
	} `json:"scopes"`
}

type Compliance struct {
	Passing int `json:"passing"`
	Failing int `json:"failing"`
}

type Resolution struct {
	Resolved          int `json:"resolved"`
	PartiallyResolved int `json:"partiallyResolved"`
	Unresolved        int `json:"unresolved"`
}

type Stats struct {
	Risk       Risk       `json:"risk"`
	Compliance Compliance `json:"compliance"`
	Resolution Resolution `json:"resolution"`
}

type SoftwareInventorySummary struct {
	ID            string         `json:"id"`
	Organization  Stats          `json:"organization"`
	SoftwareLists []SoftwareList `json:"softwareLists"`
}
<commit_msg>[DEV-341] Change Stats back to Metrics<commit_after>package projects

import "time"

const (
	// GetSoftwareListEndpoint is the path to the endpoint for retrieving a specific Software List
	GetSoftwareListEndpoint = "v1/project/getSBOM"
	// GetSoftwareListsEndpoint is the path to the endpoint for retrieving an organization's Software Lists
	GetSoftwareListsEndpoint = "v1/project/getSBOMs"
)

// SoftwareList represents a software list containing zero or more Component objects.
type SoftwareList struct {
	ID               string      `json:"id"`
	Name             string      `json:"name"`
	Version          string      `json:"version"`
	Supplier         string      `json:"supplier"`
	ContactName      string      `json:"contact_name"`
	ContactEmail     string      `json:"contact_email"`
	MonitorFrequency string      `json:"monitor_frequency"`
	Status           string      `json:"status"`
	CreatedAt        time.Time   `json:"created_at"`
	UpdatedAt        time.Time   `json:"updated_at"`
	DeletedAt        *time.Time  `json:"deleted_at"`
	EntryCount       int         `json:"entry_count"`
	Metrics          Metrics     `json:"metrics"`
	Entries          []Component `json:"entries"`
	TeamID           string      `json:"team_id"`
	OrgID            string      `json:"org_id"`
	RulesetID        string      `json:"ruleset_id"`
}

type Risk struct {
	Score  int `json:"score"`
	Scopes struct {
		Ecosystem   int `json:"ecosystem"`
		SupplyChain int `json:"supplyChain"`
		Software    int `json:"software"`
	} `json:"scopes"`
}

type Compliance struct {
	Passing int `json:"passing"`
	Failing int `json:"failing"`
}

type Resolution struct {
	Resolved          int `json:"resolved"`
	PartiallyResolved int `json:"partiallyResolved"`
	Unresolved        int `json:"unresolved"`
}

type Metrics struct {
	Risk       Risk       `json:"risk"`
	Compliance Compliance `json:"compliance"`
	Resolution Resolution `json:"resolution"`
}

type SoftwareInventorySummary struct {
	ID            string         `json:"id"`
	Organization  Metrics        `json:"organization"`
	SoftwareLists []SoftwareList `json:"softwareLists"`
}
<|endoftext|>


<commit_before>package strscan

import (
  "regexp"
  "unicode/utf8"
)

type StringScanner struct {
  String string
  offset int
  matched string
}

func New(s string) *StringScanner {
  return &StringScanner {
    String: s,
    offset: 0,
    matched: "",
  }
}

func (self *StringScanner) Check(pattern string) string {
  // This returns the value that scan would return, without advancing the scan pointer. The match register is affected, though.
  re := mustCompile(pattern)
  if !self.IsEOS() {
    return re.FindString(self.String[self.offset:])
  }
  return ""
}

func (self *StringScanner) CheckUntil(pattern string) string {
  offset := self.offset
  re := mustCompile(pattern)
  for !self.IsEOS() {
    matched := re.FindString(self.String[offset:])
    if matched != "" {
      self.matched = matched
      return self.Peek(offset - self.offset + len(matched))
    }
    _, size := utf8.DecodeRuneInString(self.String[offset:])
    offset += size
  }
  return ""
}

func (self *StringScanner) IsEOS() bool {
  return len(self.String) <= self.offset
}

func (self *StringScanner) Match(pattern string) int {
  // Tests whether the given pattern is matched from the current scan pointer. Returns the length of the match, or nil. The scan pointer is not advanced.
  re := mustCompile(pattern)
  if !self.IsEOS() {
    matched := re.FindString(self.String[self.offset:])
    if matched != "" {
      self.matched = matched
      return len(matched)
    }
  }
  return 0
}

func (self *StringScanner) Matched() string {
  return self.matched
}

func (self *StringScanner) Peek(length int) string {
  // Extracts a string corresponding to string[pos,len], without advancing the scan pointer.
  var s string
  for i, offset := 0, self.offset; i < length && offset < len(self.String); i++ {
    r, size := utf8.DecodeRuneInString(self.String[offset:])
    offset += size
    p := make([]byte, size)
    utf8.EncodeRune(p, r)
    s += string(p)
  }
  return s
}

func (self *StringScanner) Pos() int {
  return self.offset
}

func (self *StringScanner) Seek(offset int) int {
  self.offset = offset
  return offset
}

func (self *StringScanner) Scan(pattern string) string {
  // Tries to match with pattern at the current position. If there’s a match, the scanner advances the “scan pointer” and returns the matched string. Otherwise, the scanner returns nil.
  re := mustCompile(pattern)
  if !self.IsEOS() {
    matched := re.FindString(self.String[self.offset:])
    if matched != "" {
      self.offset += len(matched)
      self.matched = matched
      return matched
    }
  }
  return ""
}

func (self *StringScanner) ScanUntil(pattern string) string {
  offset := self.offset
  re := mustCompile(pattern)
  for !self.IsEOS() {
    matched := re.FindString(self.String[self.offset:])
    if matched != "" {
      self.offset += len(matched)
      self.matched = self.String[offset:self.offset]
      return self.matched
    }
    self.skipRune()
  }
  return ""
}

func (self *StringScanner) Skip(pattern string) int {
  // Attempts to skip over the given pattern beginning with the scan pointer. If it matches, the scan pointer is advanced to the end of the match, and the length of the match is returned. Otherwise, nil is returned.
  re := mustCompile(pattern)
  if !self.IsEOS() {
    matched := re.FindString(self.String[self.offset:])
    if matched != "" {
      self.offset += len(matched)
      self.matched = matched
      return len(matched)
    }
  }
  return 0
}

func (self *StringScanner) SkipUntil(pattern string) int {
  // Advances the scan pointer until pattern is matched and consumed. Returns the number of bytes advanced, or nil if no match was found.
  //
  // Look ahead to match pattern, and advance the scan pointer to the end of the match. Return the number of characters advanced, or nil if the match was unsuccessful.
  //
  // It’s similar to scan_until, but without returning the intervening string.
  re := mustCompile(pattern)
  for !self.IsEOS() {
    matched := re.FindString(self.String[self.offset:])
    if matched != "" {
      self.offset += len(matched)
      self.matched = matched
      return len(matched)
    }
    self.skipRune()
  }
  return 0
}

func mustCompile(pattern string) *regexp.Regexp {
  // FIXME: bad naming
  return regexp.MustCompile("\\A" + pattern)
}

func (self *StringScanner) skipRune() {
  if !self.IsEOS() {
    _, size := utf8.DecodeRuneInString(self.String[self.offset:])
    self.offset += size
  }
}
<commit_msg>Set regexp flags to change behaviour with newlines<commit_after>package strscan

import (
  "regexp"
  "unicode/utf8"
)

type StringScanner struct {
  String string
  offset int
  matched string
}

func New(s string) *StringScanner {
  return &StringScanner {
    String: s,
    offset: 0,
    matched: "",
  }
}

func (self *StringScanner) Check(pattern string) string {
  // This returns the value that scan would return, without advancing the scan pointer. The match register is affected, though.
  re := mustCompile(pattern)
  if !self.IsEOS() {
    return re.FindString(self.String[self.offset:])
  }
  return ""
}

func (self *StringScanner) CheckUntil(pattern string) string {
  offset := self.offset
  re := mustCompile(pattern)
  for !self.IsEOS() {
    matched := re.FindString(self.String[offset:])
    if matched != "" {
      self.matched = matched
      return self.Peek(offset - self.offset + len(matched))
    }
    _, size := utf8.DecodeRuneInString(self.String[offset:])
    offset += size
  }
  return ""
}

func (self *StringScanner) IsEOS() bool {
  return len(self.String) <= self.offset
}

func (self *StringScanner) Match(pattern string) int {
  // Tests whether the given pattern is matched from the current scan pointer. Returns the length of the match, or nil. The scan pointer is not advanced.
  re := mustCompile(pattern)
  if !self.IsEOS() {
    matched := re.FindString(self.String[self.offset:])
    if matched != "" {
      self.matched = matched
      return len(matched)
    }
  }
  return 0
}

func (self *StringScanner) Matched() string {
  return self.matched
}

func (self *StringScanner) Peek(length int) string {
  // Extracts a string corresponding to string[pos,len], without advancing the scan pointer.
  var s string
  for i, offset := 0, self.offset; i < length && offset < len(self.String); i++ {
    r, size := utf8.DecodeRuneInString(self.String[offset:])
    offset += size
    p := make([]byte, size)
    utf8.EncodeRune(p, r)
    s += string(p)
  }
  return s
}

func (self *StringScanner) Pos() int {
  return self.offset
}

func (self *StringScanner) Seek(offset int) int {
  self.offset = offset
  return offset
}

func (self *StringScanner) Scan(pattern string) string {
  // Tries to match with pattern at the current position. If there’s a match, the scanner advances the “scan pointer” and returns the matched string. Otherwise, the scanner returns nil.
  re := mustCompile(pattern)
  if !self.IsEOS() {
    matched := re.FindString(self.String[self.offset:])
    if matched != "" {
      self.offset += len(matched)
      self.matched = matched
      return matched
    }
  }
  return ""
}

func (self *StringScanner) ScanUntil(pattern string) string {
  offset := self.offset
  re := mustCompile(pattern)
  for !self.IsEOS() {
    matched := re.FindString(self.String[self.offset:])
    if matched != "" {
      self.offset += len(matched)
      self.matched = self.String[offset:self.offset]
      return self.matched
    }
    self.skipRune()
  }
  return ""
}

func (self *StringScanner) Skip(pattern string) int {
  // Attempts to skip over the given pattern beginning with the scan pointer. If it matches, the scan pointer is advanced to the end of the match, and the length of the match is returned. Otherwise, nil is returned.
  re := mustCompile(pattern)
  if !self.IsEOS() {
    matched := re.FindString(self.String[self.offset:])
    if matched != "" {
      self.offset += len(matched)
      self.matched = matched
      return len(matched)
    }
  }
  return 0
}

func (self *StringScanner) SkipUntil(pattern string) int {
  // Advances the scan pointer until pattern is matched and consumed. Returns the number of bytes advanced, or nil if no match was found.
  //
  // Look ahead to match pattern, and advance the scan pointer to the end of the match. Return the number of characters advanced, or nil if the match was unsuccessful.
  //
  // It’s similar to scan_until, but without returning the intervening string.
  re := mustCompile(pattern)
  for !self.IsEOS() {
    matched := re.FindString(self.String[self.offset:])
    if matched != "" {
      self.offset += len(matched)
      self.matched = matched
      return len(matched)
    }
    self.skipRune()
  }
  return 0
}

func mustCompile(pattern string) *regexp.Regexp {
  return regexp.MustCompile("^(?ms)" + pattern)
}

func (self *StringScanner) skipRune() {
  if !self.IsEOS() {
    _, size := utf8.DecodeRuneInString(self.String[self.offset:])
    self.offset += size
  }
}
<|endoftext|>


<commit_before>package system

import (
	"testing"

	"github.com/ready-steady/support/assert"
)

func TestNewProfile(t *testing.T) {
	plat, app, _ := Load(findFixture("002_040"))

	prof := NewProfile(plat, app)

	mobility := []float64{
		0.0000, 0.0000, 0.0000, 0.0445, 0.0000, 0.0140, 0.1145, 0.0000,
		0.0095, 0.0000, 0.0145, 0.0140, 0.0140, 0.0280, 0.0740, 0.0000,
		0.0280, 0.0140, 0.0225, 0.0000, 0.0000, 0.0225, 0.0140, 0.0310,
		0.0000, 0.0380, 0.0260, 0.0225, 0.0140, 0.0140, 0.0000, 0.0280,
		0.1490, 0.0340, 0.0225, 0.0520, 0.0280, 0.0435, 0.0380, 0.0380,
	}

	assert.AlmostEqual(prof.Mobility, mobility, t)
}
<commit_msg>Replaced AlmostEqual with EqualWithin<commit_after>package system

import (
	"testing"

	"github.com/ready-steady/support/assert"
)

func TestNewProfile(t *testing.T) {
	plat, app, _ := Load(findFixture("002_040"))

	prof := NewProfile(plat, app)

	mobility := []float64{
		0.0000, 0.0000, 0.0000, 0.0445, 0.0000, 0.0140, 0.1145, 0.0000,
		0.0095, 0.0000, 0.0145, 0.0140, 0.0140, 0.0280, 0.0740, 0.0000,
		0.0280, 0.0140, 0.0225, 0.0000, 0.0000, 0.0225, 0.0140, 0.0310,
		0.0000, 0.0380, 0.0260, 0.0225, 0.0140, 0.0140, 0.0000, 0.0280,
		0.1490, 0.0340, 0.0225, 0.0520, 0.0280, 0.0435, 0.0380, 0.0380,
	}

	assert.EqualWithin(prof.Mobility, mobility, 1e-15, t)
}
<|endoftext|>


<commit_before>package player

import (
	"errors"
	"fmt"
	"github.com/SvenWiltink/go-MusicBot/songplayer"
	"github.com/sirupsen/logrus"
	"github.com/vansante/go-event-emitter"
	"math/rand"
	"strings"
	"sync"
	"time"
)

type Player struct {
	*eventemitter.Emitter

	currentSong songplayer.Playable
	queue       []songplayer.Playable
	status      Status

	players       []songplayer.SongPlayer
	currentPlayer songplayer.SongPlayer

	playTimer         *time.Timer
	endTime           time.Time
	remainingDuration time.Duration

	controlMutex sync.Mutex
}

var ErrNothingPlaying = errors.New("nothing currently playing")

func NewPlayer() (player *Player) {
	player = &Player{
		Emitter: eventemitter.NewEmitter(),
		status:  STOPPED,
	}
	return
}

func (p *Player) GetSongPlayer(name string) (songPlayer songplayer.SongPlayer) {
	for _, plr := range p.players {
		if strings.ToLower(plr.Name()) == strings.ToLower(name) {
			songPlayer = plr
			return
		}
	}
	return
}

func (p *Player) GetSongPlayers() (players []songplayer.SongPlayer) {
	return p.players
}

func (p *Player) AddSongPlayer(player songplayer.SongPlayer) {
	logrus.Infof("Player.AddSongPlayer: Songplayer %s added", player.Name())
	p.players = append(p.players, player)
}

func (p *Player) GetQueuedSongs() (songs []songplayer.Playable) {
	return p.queue
}

func (p *Player) GetCurrentSong() (song songplayer.Playable, remaining time.Duration) {
	song = p.currentSong
	switch p.status {
	case PLAYING:
		remaining = p.endTime.Sub(time.Now())
	case PAUSED:
		remaining = p.remainingDuration
	case STOPPED:
		if p.currentSong != nil {
			remaining = p.currentSong.GetDuration()
		}
	}
	return
}

func (p *Player) findPlayer(url string) (songPlayer songplayer.SongPlayer, err error) {
	for _, play := range p.players {
		if play.CanPlay(url) {
			songPlayer = play
			return
		}
	}
	err = fmt.Errorf("No suitable songplayer found to play %s", url)
	return
}

func (p *Player) AddSongs(url string) (addedSongs []songplayer.Playable, err error) {
	p.controlMutex.Lock()
	defer p.controlMutex.Unlock()

	addedSongs, err = p.insertSongs(url, len(p.queue))
	if err != nil {
		logrus.Warnf("Player.AddSongs: Error adding songs [%s] %v", url, err)
		return
	}
	logrus.Infof("Player.AddSongs: Added %d songs from url [%s]", len(addedSongs), url)
	return
}

func (p *Player) InsertSongs(url string, position int) (addedSongs []songplayer.Playable, err error) {
	p.controlMutex.Lock()
	defer p.controlMutex.Unlock()

	if position < 0 || position > len(p.queue) {
		err = errors.New("invalid position to insert items")
		return
	}

	addedSongs, err = p.insertSongs(url, position)
	if err != nil {
		logrus.Warnf("Player.InsertSongs: Error inserting songs [%s] %v", url, err)
		return
	}
	logrus.Infof("Player.InsertSongs: Inserted %d songs from url [%s]", len(addedSongs), url)
	return
}

func (p *Player) insertSongs(url string, position int) (addedSongs []songplayer.Playable, err error) {
	musicPlayer, err := p.findPlayer(url)
	if err != nil {
		logrus.Infof("Player.insertSongs: No songplayer found to play %s", url)
		return
	}

	addedSongs, err = musicPlayer.GetSongs(url)
	if err != nil {
		logrus.Warnf("Player.insertSongs: Error getting songs from url [%s] %v", musicPlayer.Name(), err)
		return
	}

	for i, addSong := range addedSongs {
		p.queue = append(p.queue, nil)
		copy(p.queue[position+i+1:], p.queue[position+i:])
		p.queue[position+i] = addSong
	}

	p.EmitEvent("songs_added", addedSongs, position)
	p.EmitEvent("queue_updated", p.queue)
	return
}

func (p *Player) ShuffleQueue() {
	p.controlMutex.Lock()
	defer p.controlMutex.Unlock()

	for i := range p.queue {
		j := rand.Intn(i + 1)
		p.queue[i], p.queue[j] = p.queue[j], p.queue[i]
	}
	p.EmitEvent("queue_updated", p.queue)

	logrus.Infof("Player.ShuffleQueue: Queue successfully shuffled")
}

func (p *Player) EmptyQueue() {
	p.controlMutex.Lock()
	defer p.controlMutex.Unlock()

	p.queue = make([]songplayer.Playable, 0)
	p.EmitEvent("queue_updated", p.queue)

	logrus.Infof("Player.ShuffleQueue: Queue successfully emptied")
}

func (p *Player) GetStatus() (status Status) {
	return p.status
}

func (p *Player) Play() (song songplayer.Playable, err error) {
	p.controlMutex.Lock()
	defer p.controlMutex.Unlock()

	switch p.status {
	case STOPPED:
		song, err = p.next()
		return
	case PAUSED:
		err = p.pause()
	}
	song = p.currentSong
	return
}

func (p *Player) playWait() {
	p.playTimer = time.NewTimer(p.endTime.Sub(time.Now()))

	// Wait for the timer to time out, or be canceled because of a STOP or something
	<-p.playTimer.C

	p.controlMutex.Lock()
	defer p.controlMutex.Unlock()

	p.EmitEvent("play_done", p.currentSong)
	p.currentSong = nil

	if len(p.queue) == 0 {
		p.stop()
	} else if len(p.queue) > 0 && p.status == PLAYING {
		p.next()
	}
}

func (p *Player) Seek(positionSeconds int) (err error) {
	p.controlMutex.Lock()
	defer p.controlMutex.Unlock()

	if p.status == STOPPED || p.currentPlayer == nil {
		err = ErrNothingPlaying
		return
	}

	totalSeconds := int(p.currentSong.GetDuration().Seconds())
	if positionSeconds < 0 || positionSeconds > totalSeconds {
		err = fmt.Errorf("Position %d is out of bounds [0 - %d]", positionSeconds, totalSeconds)
		return
	}

	err = p.currentPlayer.Seek(positionSeconds)
	if err != nil {
		logrus.Warnf("Player.Seek: Error seeking to %d with player %s: %v", positionSeconds, p.currentPlayer.Name(), err)
		return
	}

	positionDuration := time.Duration(int64(time.Second) * int64(positionSeconds))
	remainingDuration := p.currentSong.GetDuration() - positionDuration

	p.playTimer.Reset(remainingDuration)
	p.endTime = time.Now().Add(remainingDuration)
	p.EmitEvent("song_seek", p.currentSong, remainingDuration)

	logrus.Infof("Player.Seek: Play resumed for remaining duration of %v", remainingDuration)
	return
}

func (p *Player) Next() (song songplayer.Playable, err error) {
	p.controlMutex.Lock()
	defer p.controlMutex.Unlock()

	return p.next()
}

func (p *Player) next() (song songplayer.Playable, err error) {
	if len(p.queue) == 0 {
		err = errors.New("Playlist is empty, no next available")
		return
	}
	if p.status == PLAYING || p.status == PAUSED {
		p.stop()
	}

	song, p.queue = p.queue[0], p.queue[1:]
	musicPlayer, err := p.findPlayer(song.GetURL())
	if err != nil {
		logrus.Errorf("Player.next: No player available to play [%s] %v", song.GetURL(), err)
		return
	}
	err = musicPlayer.Play(song.GetURL())
	if err != nil {
		logrus.Errorf("Player.next: Error playing %s with player %s: %v", song.GetURL(), musicPlayer.Name(), err)
		return
	}
	p.currentSong = song
	p.currentPlayer = musicPlayer
	p.status = PLAYING
	p.endTime = time.Now().Add(song.GetDuration())
	// Start waiting for the song to be done
	go p.playWait()
	p.EmitEvent("play_start", p.currentSong)
	p.EmitEvent("queue_updated", p.queue)

	logrus.Infof("Player.next: %s started playing %s successfully", musicPlayer.Name(), song.GetURL())
	return
}

func (p *Player) Stop() (err error) {
	p.controlMutex.Lock()
	defer p.controlMutex.Unlock()

	return p.stop()
}

func (p *Player) stop() (err error) {
	if p.status == STOPPED || p.currentPlayer == nil {
		err = ErrNothingPlaying
		return
	}
	currentPlayer := p.currentPlayer
	err = currentPlayer.Stop()
	if err != nil {
		logrus.Errorf("Player.stop: Error stopping player %s: %v", currentPlayer.Name(), err)
		return
	}
	p.status = STOPPED
	p.currentSong = nil
	p.currentPlayer = nil
	if p.playTimer != nil {
		// Kill the current playWait()
		p.playTimer.Stop()
	}
	p.EmitEvent("stop")

	logrus.Infof("Player.stop: %s stopped playing", currentPlayer.Name())
	return
}

func (p *Player) Pause() (err error) {
	p.controlMutex.Lock()
	defer p.controlMutex.Unlock()

	return p.pause()
}

func (p *Player) pause() (err error) {
	if p.status == STOPPED || p.currentPlayer == nil {
		err = ErrNothingPlaying
		return
	}

	err = p.currentPlayer.Pause(p.status != PAUSED)
	if err != nil {
		logrus.Errorf("Player.pause: Error (un)pausing player %s [%v]: %v", p.currentPlayer.Name(), p.status != PAUSED, err)
		return
	}
	if p.status == PAUSED {
		p.status = PLAYING
		p.endTime = time.Now().Add(p.remainingDuration)
		// Restart the play wait goroutine with the new time
		go p.playWait()

		p.EmitEvent("unpause", p.currentSong, p.remainingDuration)

		logrus.Infof("Player.pause: %s resumed playing %s", p.currentPlayer.Name(), p.currentSong.GetURL())
	} else {
		p.status = PAUSED
		p.remainingDuration = p.endTime.Sub(time.Now())
		if p.playTimer != nil {
			// Kill the current playWait()
			p.playTimer.Stop()
		}
		p.EmitEvent("pause", p.currentSong, p.remainingDuration)

		logrus.Infof("Player.pause: %s paused playing %s", p.currentPlayer.Name(), p.currentSong.GetURL())
	}
	return
}
<commit_msg>Rewrite Player queue into a proper playlist, so we keep history and can thus use Previous()<commit_after>package player

import (
	"errors"
	"fmt"
	"github.com/SvenWiltink/go-MusicBot/songplayer"
	"github.com/sirupsen/logrus"
	"github.com/vansante/go-event-emitter"
	"math/rand"
	"strings"
	"sync"
	"time"
)

type Player struct {
	*eventemitter.Emitter

	currentSong      songplayer.Playable
	playlistPosition int
	playlist         []songplayer.Playable
	status           Status

	players       []songplayer.SongPlayer
	currentPlayer songplayer.SongPlayer

	playTimer         *time.Timer
	endTime           time.Time
	remainingDuration time.Duration

	controlMutex sync.Mutex
}

var ErrNothingPlaying = errors.New("nothing currently playing")

func NewPlayer() (player *Player) {
	player = &Player{
		Emitter:          eventemitter.NewEmitter(),
		status:           STOPPED,
		playlistPosition: 0,
	}
	return
}

func (p *Player) GetSongPlayer(name string) (songPlayer songplayer.SongPlayer) {
	for _, plr := range p.players {
		if strings.ToLower(plr.Name()) == strings.ToLower(name) {
			songPlayer = plr
			return
		}
	}
	return
}

func (p *Player) GetSongPlayers() (players []songplayer.SongPlayer) {
	return p.players
}

func (p *Player) AddSongPlayer(player songplayer.SongPlayer) {
	logrus.Infof("Player.AddSongPlayer: Songplayer %s added", player.Name())
	p.players = append(p.players, player)
}

func (p *Player) GetQueuedSongs() (songs []songplayer.Playable) {
	return p.playlist[p.playlistPosition:]
}

func (p *Player) GetCurrentSong() (song songplayer.Playable, remaining time.Duration) {
	song = p.currentSong
	switch p.status {
	case PLAYING:
		remaining = p.endTime.Sub(time.Now())
	case PAUSED:
		remaining = p.remainingDuration
	case STOPPED:
		if p.currentSong != nil {
			remaining = p.currentSong.GetDuration()
		}
	}
	return
}

func (p *Player) findPlayer(url string) (songPlayer songplayer.SongPlayer, err error) {
	for _, play := range p.players {
		if play.CanPlay(url) {
			songPlayer = play
			return
		}
	}
	err = fmt.Errorf("No suitable songplayer found to play %s", url)
	return
}

func (p *Player) AddSongs(url string) (addedSongs []songplayer.Playable, err error) {
	p.controlMutex.Lock()
	defer p.controlMutex.Unlock()

	addedSongs, err = p.insertSongs(url, len(p.playlist))
	if err != nil {
		logrus.Warnf("Player.AddSongs: Error adding songs [%s] %v", url, err)
		return
	}
	logrus.Infof("Player.AddSongs: Added %d songs from url [%s]", len(addedSongs), url)
	return
}

func (p *Player) InsertSongs(url string, position int) (addedSongs []songplayer.Playable, err error) {
	p.controlMutex.Lock()
	defer p.controlMutex.Unlock()

	// Convert position by offsetting it against current position
	position += p.playlistPosition
	if position < 0 || position > len(p.playlist) {
		err = errors.New("invalid position to insert songs")
		return
	}

	addedSongs, err = p.insertSongs(url, position)
	if err != nil {
		logrus.Warnf("Player.InsertSongs: Error inserting songs [%s] %v", url, err)
		return
	}
	logrus.Infof("Player.InsertSongs: Inserted %d songs from url [%s]", len(addedSongs), url)
	return
}

func (p *Player) insertSongs(url string, position int) (addedSongs []songplayer.Playable, err error) {
	musicPlayer, err := p.findPlayer(url)
	if err != nil {
		logrus.Infof("Player.insertSongs: No songplayer found to play %s", url)
		return
	}

	addedSongs, err = musicPlayer.GetSongs(url)
	if err != nil {
		logrus.Warnf("Player.insertSongs: Error getting songs from url [%s] %v", musicPlayer.Name(), err)
		return
	}

	for i, addSong := range addedSongs {
		p.playlist = append(p.playlist, nil)
		copy(p.playlist[position+i+1:], p.playlist[position+i:])
		p.playlist[position+i] = addSong
	}

	p.EmitEvent("songs_added", addedSongs, position)
	p.EmitEvent("queue_updated", p.playlist[p.playlistPosition:])
	return
}

func (p *Player) ShuffleQueue() {
	p.controlMutex.Lock()
	defer p.controlMutex.Unlock()

	for i := p.playlistPosition + 1; i < len(p.playlist); i++ {
		j := rand.Intn(i + 1)
		p.playlist[i], p.playlist[j] = p.playlist[j], p.playlist[i]
	}
	p.EmitEvent("queue_updated", p.playlist[p.playlistPosition:])

	logrus.Infof("Player.ShuffleQueue: Queue successfully shuffled")
}

func (p *Player) EmptyQueue() {
	p.controlMutex.Lock()
	defer p.controlMutex.Unlock()

	newList := make([]songplayer.Playable, 0)
	// Copy over the play history
	for i := 0; i <= p.playlistPosition; i++ {
		newList[i] = p.playlist[i]
	}
	p.playlist = newList

	p.EmitEvent("queue_updated", p.playlist[p.playlistPosition:])

	logrus.Infof("Player.ShuffleQueue: Queue successfully emptied")
}

func (p *Player) GetStatus() (status Status) {
	return p.status
}

func (p *Player) Play() (song songplayer.Playable, err error) {
	p.controlMutex.Lock()
	defer p.controlMutex.Unlock()

	switch p.status {
	case STOPPED:
		song, err = p.setPlaylistPosition(p.playlistPosition + 1)
		return
	case PAUSED:
		err = p.pause()
	}
	song = p.currentSong
	return
}

func (p *Player) playWait() {
	p.playTimer = time.NewTimer(p.endTime.Sub(time.Now()))

	// Wait for the timer to time out, or be canceled because of a STOP or something
	<-p.playTimer.C

	p.controlMutex.Lock()
	defer p.controlMutex.Unlock()

	p.EmitEvent("play_done", p.currentSong)
	p.currentSong = nil

	if len(p.playlist)-1 < p.playlistPosition && p.status == PLAYING {
		p.setPlaylistPosition(p.playlistPosition + 1)
	} else {
		p.stop()
	}
}

func (p *Player) Seek(positionSeconds int) (err error) {
	p.controlMutex.Lock()
	defer p.controlMutex.Unlock()

	if p.status == STOPPED || p.currentPlayer == nil {
		err = ErrNothingPlaying
		return
	}

	totalSeconds := int(p.currentSong.GetDuration().Seconds())
	if positionSeconds < 0 || positionSeconds > totalSeconds {
		err = fmt.Errorf("Position %d is out of bounds [0 - %d]", positionSeconds, totalSeconds)
		return
	}

	err = p.currentPlayer.Seek(positionSeconds)
	if err != nil {
		logrus.Warnf("Player.Seek: Error seeking to %d with player %s: %v", positionSeconds, p.currentPlayer.Name(), err)
		return
	}

	positionDuration := time.Duration(int64(time.Second) * int64(positionSeconds))
	remainingDuration := p.currentSong.GetDuration() - positionDuration

	p.playTimer.Reset(remainingDuration)
	p.endTime = time.Now().Add(remainingDuration)
	p.EmitEvent("song_seek", p.currentSong, remainingDuration)

	logrus.Infof("Player.Seek: Play resumed for remaining duration of %v", remainingDuration)
	return
}

func (p *Player) Next() (song songplayer.Playable, err error) {
	p.controlMutex.Lock()
	defer p.controlMutex.Unlock()

	if p.playlistPosition + 1 == len(p.playlist) {
		err = errors.New("no next available, queue is empty")
	}

	return p.setPlaylistPosition(p.playlistPosition + 1)
}

func (p *Player) setPlaylistPosition(newPosition int) (song songplayer.Playable, err error) {
	if newPosition < 0 || len(p.playlist) == 0 || newPosition >= len(p.playlist) {
		err = errors.New("invalid playlist position")
		return
	}
	if p.status == PLAYING || p.status == PAUSED {
		p.stop()
	}

	song = p.playlist[newPosition]
	musicPlayer, err := p.findPlayer(song.GetURL())
	if err != nil {
		logrus.Errorf("Player.next: No player available to play [%s] %v", song.GetURL(), err)
		return
	}
	err = musicPlayer.Play(song.GetURL())
	if err != nil {
		logrus.Errorf("Player.next: Error playing %s with player %s: %v", song.GetURL(), musicPlayer.Name(), err)
		return
	}
	p.playlistPosition = newPosition
	p.currentSong = song
	p.currentPlayer = musicPlayer
	p.status = PLAYING
	p.endTime = time.Now().Add(song.GetDuration())

	// Start waiting for the song to be done
	go p.playWait()
	p.EmitEvent("play_start", p.currentSong)
	p.EmitEvent("queue_updated", p.playlist[p.playlistPosition:])

	logrus.Infof("Player.next: %s started playing %s successfully", musicPlayer.Name(), song.GetURL())
	return
}

func (p *Player) Previous() (song songplayer.Playable, err error) {
	p.controlMutex.Lock()
	defer p.controlMutex.Unlock()

	if p.playlistPosition == 0 {
		err = errors.New("no previous available, history is empty")
	}

	return p.setPlaylistPosition(p.playlistPosition-1)
}

func (p *Player) Stop() (err error) {
	p.controlMutex.Lock()
	defer p.controlMutex.Unlock()

	return p.stop()
}

func (p *Player) stop() (err error) {
	if p.status == STOPPED || p.currentPlayer == nil {
		err = ErrNothingPlaying
		return
	}
	currentPlayer := p.currentPlayer
	err = currentPlayer.Stop()
	if err != nil {
		logrus.Errorf("Player.stop: Error stopping player %s: %v", currentPlayer.Name(), err)
		return
	}
	p.status = STOPPED
	p.currentSong = nil
	p.currentPlayer = nil
	if p.playTimer != nil {
		// Kill the current playWait()
		p.playTimer.Stop()
	}
	p.EmitEvent("stop")

	logrus.Infof("Player.stop: %s stopped playing", currentPlayer.Name())
	return
}

func (p *Player) Pause() (err error) {
	p.controlMutex.Lock()
	defer p.controlMutex.Unlock()

	return p.pause()
}

func (p *Player) pause() (err error) {
	if p.status == STOPPED || p.currentPlayer == nil {
		err = ErrNothingPlaying
		return
	}

	err = p.currentPlayer.Pause(p.status != PAUSED)
	if err != nil {
		logrus.Errorf("Player.pause: Error (un)pausing player %s [%v]: %v", p.currentPlayer.Name(), p.status != PAUSED, err)
		return
	}
	if p.status == PAUSED {
		p.status = PLAYING
		p.endTime = time.Now().Add(p.remainingDuration)
		// Restart the play wait goroutine with the new time
		go p.playWait()

		p.EmitEvent("unpause", p.currentSong, p.remainingDuration)

		logrus.Infof("Player.pause: %s resumed playing %s", p.currentPlayer.Name(), p.currentSong.GetURL())
	} else {
		p.status = PAUSED
		p.remainingDuration = p.endTime.Sub(time.Now())
		if p.playTimer != nil {
			// Kill the current playWait()
			p.playTimer.Stop()
		}
		p.EmitEvent("pause", p.currentSong, p.remainingDuration)

		logrus.Infof("Player.pause: %s paused playing %s", p.currentPlayer.Name(), p.currentSong.GetURL())
	}
	return
}
<|endoftext|>


<commit_before>package player

import (
	"github.com/lean-poker/poker-player-go/leanpoker"
	"strings"
)

const VERSION = "Default Go folding player"

func BetRequest(state *leanpoker.Game) int {

	p := state.GetPlayer()

	if p == nil {
		return 1000
	}

	var bet int

	if (state.IsCheckable(bet)) {
		return bet
	}

	if isPair(p.HoleCards) {
		bet += state.CurrentBuyIn * rankHoleCards(p.HoleCards)
	}

	if (state.CurrentBuyIn > state.CurrentBuyIn * rankHoleCards(p.HoleCards)) {
		return 0
	}

	/*if (len(state.CommunityCards) > 0) {
		bet += calcComunityCards(state, bet)
	}*/

	return bet
}

func Showdown(state *leanpoker.Game) {

}

func Version() string {
	return VERSION
}

func isPair(cards []*leanpoker.Card) bool  {
	if len(cards) < 2 {
		return false
	}

	return cards[0].Rank == cards[1].Rank
}

func rankHoleCards(cards []*leanpoker.Card) int {
	if len(cards) < 2 {
		return 0
	}

	card1 := rankCard(cards[0].Rank)
	card2 := rankCard(cards[1].Rank)

	return card1 + card2
}

func rankCard(rank string) int {
	ranks := "12345678910JQKA"
	return strings.Index(ranks, rank) + 1
}

/*func calcComunityCards(g *leanpoker.Game, currentBet) {

}*/

<commit_msg>bet the rank<commit_after>package player

import (
	"github.com/lean-poker/poker-player-go/leanpoker"
	"strings"
)

const VERSION = "Default Go folding player"

func BetRequest(state *leanpoker.Game) int {

	p := state.GetPlayer()

	if p == nil {
		return 1000
	}

	var bet int

	if (state.IsCheckable(bet)) {
		return bet
	}

	if isPair(p.HoleCards) {
		bet += state.CurrentBuyIn * rankHoleCards(p.HoleCards)
	}

	if (state.CurrentBuyIn > state.CurrentBuyIn * rankHoleCards(p.HoleCards)) {
		return 0
	}

	bet += rankHoleCards(p.HoleCards) * 2

	return bet
}

func Showdown(state *leanpoker.Game) {

}

func Version() string {
	return VERSION
}

func isPair(cards []*leanpoker.Card) bool  {
	if len(cards) < 2 {
		return false
	}

	return cards[0].Rank == cards[1].Rank
}

func rankHoleCards(cards []*leanpoker.Card) int {
	if len(cards) < 2 {
		return 0
	}

	card1 := rankCard(cards[0].Rank)
	card2 := rankCard(cards[1].Rank)

	return card1 + card2
}

func rankCard(rank string) int {
	ranks := "12345678910JQKA"
	return strings.Index(ranks, rank) + 1
}

/*func calcComunityCards(g *leanpoker.Game, currentBet) {

}*/

<|endoftext|>