<commit_before>package dalga

// TODO implement raft consensus

import (
	"database/sql"
	"flag"
	"log"
	"net"
	"strconv"
	"time"

	"github.com/cenkalti/dalga/vendor/github.com/go-sql-driver/mysql"
	"github.com/cenkalti/dalga/vendor/github.com/streadway/amqp"
)

var debugging = flag.Bool("debug", false, "turn on debug messages")

func debug(args ...interface{}) {
	if *debugging {
		log.Println(args...)
	}
}

type Dalga struct {
	config     Config
	db         *sql.DB
	table      *table
	connection *amqp.Connection
	channel    *amqp.Channel
	listener   net.Listener
	// to wake up publisher when a new job is scheduled or cancelled
	notify chan struct{}
	// will be closed when dalga is ready to accept requests
	ready chan struct{}
	// will be closed by Shutdown method
	shutdown chan struct{}
	// to stop publisher goroutine
	stopPublisher chan struct{}
	// will be closed when publisher goroutine is stopped
	publisherStopped chan struct{}
}

func New(config Config) *Dalga {
	return &Dalga{
		config:           config,
		notify:           make(chan struct{}, 1),
		ready:            make(chan struct{}),
		shutdown:         make(chan struct{}),
		stopPublisher:    make(chan struct{}),
		publisherStopped: make(chan struct{}),
	}
}

// Run the dalga and waits until Shutdown() is called.
func (d *Dalga) Run() error {
	if err := d.connectDB(); err != nil {
		return err
	}
	defer d.db.Close()

	if err := d.connectMQ(); err != nil {
		return err
	}
	defer d.channel.Close()
	defer d.connection.Close()

	var err error
	d.listener, err = net.Listen("tcp", d.config.HTTP.Addr())
	if err != nil {
		return err
	}
	log.Println("Listening", d.listener.Addr())

	close(d.ready)

	go d.publisher()
	defer func() {
		close(d.stopPublisher)
		<-d.publisherStopped
	}()

	if err = d.serveHTTP(); err != nil {
		select {
		case _, ok := <-d.shutdown:
			if !ok {
				// shutdown in progress, do not return error
				return nil
			}
		default:
		}
	}
	return err
}

func (d *Dalga) Shutdown() error {
	close(d.shutdown)
	return d.listener.Close()
}

func (d *Dalga) NotifyReady() <-chan struct{} {
	return d.ready
}

func (d *Dalga) connectDB() error {
	var err error
	d.db, err = sql.Open("mysql", d.config.MySQL.DSN())
	if err != nil {
		return err
	}
	if err = d.db.Ping(); err != nil {
		return err
	}
	log.Print("Connected to MySQL")
	d.table = &table{d.db, d.config.MySQL.Table}
	return nil
}

func (d *Dalga) connectMQ() error {
	var err error
	d.connection, err = amqp.Dial(d.config.RabbitMQ.URL())
	if err != nil {
		return err
	}
	log.Print("Connected to RabbitMQ")

	// Exit program when AMQP connection is closed.
	connClosed := make(chan *amqp.Error)
	d.connection.NotifyClose(connClosed)
	go func() {
		if err, ok := <-connClosed; ok {
			log.Fatal(err)
		}
	}()

	d.channel, err = d.connection.Channel()
	if err != nil {
		return err
	}

	// Log undelivered messages.
	returns := make(chan amqp.Return)
	d.channel.NotifyReturn(returns)
	go func() {
		for r := range returns {
			log.Printf("%d: %s exchange=%q routing-key=%q", r.ReplyCode, r.ReplyText, r.Exchange, r.RoutingKey)
		}
	}()

	return nil
}

func (d *Dalga) CreateTable() error {
	db, err := sql.Open("mysql", d.config.MySQL.DSN())
	if err != nil {
		return err
	}
	defer db.Close()
	t := &table{db, d.config.MySQL.Table}
	return t.Create()
}

func (d *Dalga) Get(description, routingKey string) (*Job, error) {
	return d.table.Get(description, routingKey)
}

func (d *Dalga) Schedule(description, routingKey string, interval uint32, oneOff bool) (*Job, error) {
	job := NewJob(description, routingKey, interval, oneOff)
	err := d.table.Insert(job)
	if err != nil {
		return nil, err
	}
	d.notifyPublisher("new job")
	debug("Job is scheduled:", job)
	return job, nil
}

func (d *Dalga) Cancel(description, routingKey string) error {
	err := d.table.Delete(description, routingKey)
	if err != nil {
		return err
	}
	d.notifyPublisher("job cancelled")
	debug("Job is cancelled")
	return nil
}

func (d *Dalga) notifyPublisher(debugMessage string) {
	select {
	case d.notify <- struct{}{}:
		debug("notifying publisher:", debugMessage)
	default:
	}
}

// publish sends a message to exchange defined in the config and
// updates the Job's next run time on the database.
func (d *Dalga) publish(j *Job) error {
	debug("publish", *j)

	err := d.channel.Publish(d.config.RabbitMQ.Exchange, j.RoutingKey, true, false, amqp.Publishing{
		Body:         []byte(j.Description),
		DeliveryMode: amqp.Persistent,
		Expiration:   strconv.FormatFloat(j.Interval.Seconds(), 'f', 0, 64) + "000",
	})
	if err != nil {
		return err
	}

	if j.Interval == 0 {
		debug("deleting one-off job")
		return d.table.Delete(j.Description, j.RoutingKey)
	}

	j.SetNewNextRun()
	return d.table.UpdateNextRun(j)
}

// publisher runs a loop that reads the next Job from the queue and publishes it.
func (d *Dalga) publisher() {
	defer close(d.publisherStopped)

	for {
		debug("---")

		var after <-chan time.Time

		job, err := d.table.Front()
		if err != nil {
			if err == sql.ErrNoRows {
				debug("No scheduled jobs in the table")
			} else if myErr, ok := err.(*mysql.MySQLError); ok && myErr.Number == 1146 {
				// Table doesn't exist
				log.Fatal(myErr)
			} else {
				log.Print(err)
				time.Sleep(time.Second)
				continue
			}
		} else {
			remaining := job.Remaining()
			after = time.After(remaining)
			debug("Next job:", job, "Remaining:", remaining)
		}

		// Sleep until the next job's run time or the webserver's wakes us up.
		select {
		case <-after:
			debug("Job sleep time finished")
			if err = d.publish(job); err != nil {
				log.Print(err)
				time.Sleep(time.Second)
			}
		case <-d.notify:
			debug("Woken up from sleep by notification")
			continue
		case <-d.stopPublisher:
			debug("Came quit message")
			return
		}
	}
}
<commit_msg>remove todo<commit_after>package dalga

import (
	"database/sql"
	"flag"
	"log"
	"net"
	"strconv"
	"time"

	"github.com/cenkalti/dalga/vendor/github.com/go-sql-driver/mysql"
	"github.com/cenkalti/dalga/vendor/github.com/streadway/amqp"
)

var debugging = flag.Bool("debug", false, "turn on debug messages")

func debug(args ...interface{}) {
	if *debugging {
		log.Println(args...)
	}
}

type Dalga struct {
	config     Config
	db         *sql.DB
	table      *table
	connection *amqp.Connection
	channel    *amqp.Channel
	listener   net.Listener
	// to wake up publisher when a new job is scheduled or cancelled
	notify chan struct{}
	// will be closed when dalga is ready to accept requests
	ready chan struct{}
	// will be closed by Shutdown method
	shutdown chan struct{}
	// to stop publisher goroutine
	stopPublisher chan struct{}
	// will be closed when publisher goroutine is stopped
	publisherStopped chan struct{}
}

func New(config Config) *Dalga {
	return &Dalga{
		config:           config,
		notify:           make(chan struct{}, 1),
		ready:            make(chan struct{}),
		shutdown:         make(chan struct{}),
		stopPublisher:    make(chan struct{}),
		publisherStopped: make(chan struct{}),
	}
}

// Run the dalga and waits until Shutdown() is called.
func (d *Dalga) Run() error {
	if err := d.connectDB(); err != nil {
		return err
	}
	defer d.db.Close()

	if err := d.connectMQ(); err != nil {
		return err
	}
	defer d.channel.Close()
	defer d.connection.Close()

	var err error
	d.listener, err = net.Listen("tcp", d.config.HTTP.Addr())
	if err != nil {
		return err
	}
	log.Println("Listening", d.listener.Addr())

	close(d.ready)

	go d.publisher()
	defer func() {
		close(d.stopPublisher)
		<-d.publisherStopped
	}()

	if err = d.serveHTTP(); err != nil {
		select {
		case _, ok := <-d.shutdown:
			if !ok {
				// shutdown in progress, do not return error
				return nil
			}
		default:
		}
	}
	return err
}

func (d *Dalga) Shutdown() error {
	close(d.shutdown)
	return d.listener.Close()
}

func (d *Dalga) NotifyReady() <-chan struct{} {
	return d.ready
}

func (d *Dalga) connectDB() error {
	var err error
	d.db, err = sql.Open("mysql", d.config.MySQL.DSN())
	if err != nil {
		return err
	}
	if err = d.db.Ping(); err != nil {
		return err
	}
	log.Print("Connected to MySQL")
	d.table = &table{d.db, d.config.MySQL.Table}
	return nil
}

func (d *Dalga) connectMQ() error {
	var err error
	d.connection, err = amqp.Dial(d.config.RabbitMQ.URL())
	if err != nil {
		return err
	}
	log.Print("Connected to RabbitMQ")

	// Exit program when AMQP connection is closed.
	connClosed := make(chan *amqp.Error)
	d.connection.NotifyClose(connClosed)
	go func() {
		if err, ok := <-connClosed; ok {
			log.Fatal(err)
		}
	}()

	d.channel, err = d.connection.Channel()
	if err != nil {
		return err
	}

	// Log undelivered messages.
	returns := make(chan amqp.Return)
	d.channel.NotifyReturn(returns)
	go func() {
		for r := range returns {
			log.Printf("%d: %s exchange=%q routing-key=%q", r.ReplyCode, r.ReplyText, r.Exchange, r.RoutingKey)
		}
	}()

	return nil
}

func (d *Dalga) CreateTable() error {
	db, err := sql.Open("mysql", d.config.MySQL.DSN())
	if err != nil {
		return err
	}
	defer db.Close()
	t := &table{db, d.config.MySQL.Table}
	return t.Create()
}

func (d *Dalga) Get(description, routingKey string) (*Job, error) {
	return d.table.Get(description, routingKey)
}

func (d *Dalga) Schedule(description, routingKey string, interval uint32, oneOff bool) (*Job, error) {
	job := NewJob(description, routingKey, interval, oneOff)
	err := d.table.Insert(job)
	if err != nil {
		return nil, err
	}
	d.notifyPublisher("new job")
	debug("Job is scheduled:", job)
	return job, nil
}

func (d *Dalga) Cancel(description, routingKey string) error {
	err := d.table.Delete(description, routingKey)
	if err != nil {
		return err
	}
	d.notifyPublisher("job cancelled")
	debug("Job is cancelled")
	return nil
}

func (d *Dalga) notifyPublisher(debugMessage string) {
	select {
	case d.notify <- struct{}{}:
		debug("notifying publisher:", debugMessage)
	default:
	}
}

// publish sends a message to exchange defined in the config and
// updates the Job's next run time on the database.
func (d *Dalga) publish(j *Job) error {
	debug("publish", *j)

	err := d.channel.Publish(d.config.RabbitMQ.Exchange, j.RoutingKey, true, false, amqp.Publishing{
		Body:         []byte(j.Description),
		DeliveryMode: amqp.Persistent,
		Expiration:   strconv.FormatFloat(j.Interval.Seconds(), 'f', 0, 64) + "000",
	})
	if err != nil {
		return err
	}

	if j.Interval == 0 {
		debug("deleting one-off job")
		return d.table.Delete(j.Description, j.RoutingKey)
	}

	j.SetNewNextRun()
	return d.table.UpdateNextRun(j)
}

// publisher runs a loop that reads the next Job from the queue and publishes it.
func (d *Dalga) publisher() {
	defer close(d.publisherStopped)

	for {
		debug("---")

		var after <-chan time.Time

		job, err := d.table.Front()
		if err != nil {
			if err == sql.ErrNoRows {
				debug("No scheduled jobs in the table")
			} else if myErr, ok := err.(*mysql.MySQLError); ok && myErr.Number == 1146 {
				// Table doesn't exist
				log.Fatal(myErr)
			} else {
				log.Print(err)
				time.Sleep(time.Second)
				continue
			}
		} else {
			remaining := job.Remaining()
			after = time.After(remaining)
			debug("Next job:", job, "Remaining:", remaining)
		}

		// Sleep until the next job's run time or the webserver's wakes us up.
		select {
		case <-after:
			debug("Job sleep time finished")
			if err = d.publish(job); err != nil {
				log.Print(err)
				time.Sleep(time.Second)
			}
		case <-d.notify:
			debug("Woken up from sleep by notification")
			continue
		case <-d.stopPublisher:
			debug("Came quit message")
			return
		}
	}
}
<|endoftext|>


<commit_before>package lxd

import (
	"io"
	"net/http"

	"github.com/gorilla/websocket"

	"github.com/lxc/lxd/shared/api"
	"github.com/lxc/lxd/shared/cancel"
)

// The Server type represents a generic read-only server.
type Server interface {
	GetConnectionInfo() (info *ConnectionInfo, err error)
	GetHTTPClient() (client *http.Client, err error)
}

// The ImageServer type represents a read-only image server.
type ImageServer interface {
	Server

	// Image handling functions
	GetImages() (images []api.Image, err error)
	GetImageFingerprints() (fingerprints []string, err error)

	GetImage(fingerprint string) (image *api.Image, ETag string, err error)
	GetImageFile(fingerprint string, req ImageFileRequest) (resp *ImageFileResponse, err error)
	GetImageSecret(fingerprint string) (secret string, err error)

	GetPrivateImage(fingerprint string, secret string) (image *api.Image, ETag string, err error)
	GetPrivateImageFile(fingerprint string, secret string, req ImageFileRequest) (resp *ImageFileResponse, err error)

	GetImageAliases() (aliases []api.ImageAliasesEntry, err error)
	GetImageAliasNames() (names []string, err error)

	GetImageAlias(name string) (alias *api.ImageAliasesEntry, ETag string, err error)
}

// The ContainerServer type represents a full featured LXD server.
type ContainerServer interface {
	ImageServer

	// Server functions
	GetServer() (server *api.Server, ETag string, err error)
	GetServerResources() (*api.Resources, error)
	UpdateServer(server api.ServerPut, ETag string) (err error)
	HasExtension(extension string) bool
	RequireAuthenticated(authenticated bool)

	// Certificate functions
	GetCertificateFingerprints() (fingerprints []string, err error)
	GetCertificates() (certificates []api.Certificate, err error)
	GetCertificate(fingerprint string) (certificate *api.Certificate, ETag string, err error)
	CreateCertificate(certificate api.CertificatesPost) (err error)
	UpdateCertificate(fingerprint string, certificate api.CertificatePut, ETag string) (err error)
	DeleteCertificate(fingerprint string) (err error)

	// Container functions
	GetContainerNames() (names []string, err error)
	GetContainers() (containers []api.Container, err error)
	GetContainer(name string) (container *api.Container, ETag string, err error)
	CreateContainer(container api.ContainersPost) (op *Operation, err error)
	CreateContainerFromImage(source ImageServer, image api.Image, imgcontainer api.ContainersPost) (op *RemoteOperation, err error)
	CopyContainer(source ContainerServer, container api.Container, args *ContainerCopyArgs) (op *RemoteOperation, err error)
	UpdateContainer(name string, container api.ContainerPut, ETag string) (op *Operation, err error)
	RenameContainer(name string, container api.ContainerPost) (op *Operation, err error)
	MigrateContainer(name string, container api.ContainerPost) (op *Operation, err error)
	DeleteContainer(name string) (op *Operation, err error)

	ExecContainer(containerName string, exec api.ContainerExecPost, args *ContainerExecArgs) (*Operation, error)

	GetContainerFile(containerName string, path string) (content io.ReadCloser, resp *ContainerFileResponse, err error)
	CreateContainerFile(containerName string, path string, args ContainerFileArgs) (err error)
	DeleteContainerFile(containerName string, path string) (err error)

	GetContainerSnapshotNames(containerName string) (names []string, err error)
	GetContainerSnapshots(containerName string) (snapshots []api.ContainerSnapshot, err error)
	GetContainerSnapshot(containerName string, name string) (snapshot *api.ContainerSnapshot, ETag string, err error)
	CreateContainerSnapshot(containerName string, snapshot api.ContainerSnapshotsPost) (op *Operation, err error)
	CopyContainerSnapshot(source ContainerServer, snapshot api.ContainerSnapshot, args *ContainerSnapshotCopyArgs) (op *RemoteOperation, err error)
	RenameContainerSnapshot(containerName string, name string, container api.ContainerSnapshotPost) (op *Operation, err error)
	MigrateContainerSnapshot(containerName string, name string, container api.ContainerSnapshotPost) (op *Operation, err error)
	DeleteContainerSnapshot(containerName string, name string) (op *Operation, err error)

	GetContainerState(name string) (state *api.ContainerState, ETag string, err error)
	UpdateContainerState(name string, state api.ContainerStatePut, ETag string) (op *Operation, err error)

	GetContainerLogfiles(name string) (logfiles []string, err error)
	GetContainerLogfile(name string, filename string) (content io.ReadCloser, err error)
	DeleteContainerLogfile(name string, filename string) (err error)

	GetContainerMetadata(name string) (*api.ImageMetadata, string, error)
	SetContainerMetadata(name string, metadata api.ImageMetadata, ETag string) error

	GetContainerTemplateFiles(containerName string) (templates []string, err error)
	GetContainerTemplateFile(containerName string, templateName string) (content io.ReadCloser, err error)
	CreateContainerTemplateFile(containerName string, templateName string, content io.ReadSeeker) (err error)
	UpdateContainerTemplateFile(containerName string, templateName string, content io.ReadSeeker) (err error)
	DeleteContainerTemplateFile(name string, templateName string) (err error)

	// Event handling functions
	GetEvents() (listener *EventListener, err error)

	// Image functions
	CreateImage(image api.ImagesPost, args *ImageCreateArgs) (op *Operation, err error)
	CopyImage(source ImageServer, image api.Image, args *ImageCopyArgs) (op *RemoteOperation, err error)
	UpdateImage(fingerprint string, image api.ImagePut, ETag string) (err error)
	DeleteImage(fingerprint string) (op *Operation, err error)
	RefreshImage(fingerprint string) (op *Operation, err error)
	CreateImageSecret(fingerprint string) (op *Operation, err error)
	CreateImageAlias(alias api.ImageAliasesPost) (err error)
	UpdateImageAlias(name string, alias api.ImageAliasesEntryPut, ETag string) (err error)
	RenameImageAlias(name string, alias api.ImageAliasesEntryPost) (err error)
	DeleteImageAlias(name string) (err error)

	// Network functions ("network" API extension)
	GetNetworkNames() (names []string, err error)
	GetNetworks() (networks []api.Network, err error)
	GetNetwork(name string) (network *api.Network, ETag string, err error)
	CreateNetwork(network api.NetworksPost) (err error)
	UpdateNetwork(name string, network api.NetworkPut, ETag string) (err error)
	RenameNetwork(name string, network api.NetworkPost) (err error)
	DeleteNetwork(name string) (err error)

	// Operation functions
	GetOperationUUIDs() (uuids []string, err error)
	GetOperations() (operations []api.Operation, err error)
	GetOperation(uuid string) (op *api.Operation, ETag string, err error)
	DeleteOperation(uuid string) (err error)
	GetOperationWebsocket(uuid string, secret string) (conn *websocket.Conn, err error)

	// Profile functions
	GetProfileNames() (names []string, err error)
	GetProfiles() (profiles []api.Profile, err error)
	GetProfile(name string) (profile *api.Profile, ETag string, err error)
	CreateProfile(profile api.ProfilesPost) (err error)
	UpdateProfile(name string, profile api.ProfilePut, ETag string) (err error)
	RenameProfile(name string, profile api.ProfilePost) (err error)
	DeleteProfile(name string) (err error)

	// Storage pool functions ("storage" API extension)
	GetStoragePoolNames() (names []string, err error)
	GetStoragePools() (pools []api.StoragePool, err error)
	GetStoragePool(name string) (pool *api.StoragePool, ETag string, err error)
	GetStoragePoolResources(name string) (resources *api.ResourcesStoragePool, err error)
	CreateStoragePool(pool api.StoragePoolsPost) (err error)
	UpdateStoragePool(name string, pool api.StoragePoolPut, ETag string) (err error)
	DeleteStoragePool(name string) (err error)

	// Storage volume functions ("storage" API extension)
	GetStoragePoolVolumeNames(pool string) (names []string, err error)
	GetStoragePoolVolumes(pool string) (volumes []api.StorageVolume, err error)
	GetStoragePoolVolume(pool string, volType string, name string) (volume *api.StorageVolume, ETag string, err error)
	CreateStoragePoolVolume(pool string, volume api.StorageVolumesPost) (err error)
	UpdateStoragePoolVolume(pool string, volType string, name string, volume api.StorageVolumePut, ETag string) (err error)
	DeleteStoragePoolVolume(pool string, volType string, name string) (err error)
	RenameStoragePoolVolume(pool string, volType string, name string, volume api.StorageVolumePost) (err error)

	// Internal functions (for internal use)
	RawQuery(method string, path string, data interface{}, queryETag string) (resp *api.Response, ETag string, err error)
	RawWebsocket(path string) (conn *websocket.Conn, err error)
}

// The ConnectionInfo struct represents general information for a connection
type ConnectionInfo struct {
	Addresses   []string
	Certificate string
	Protocol    string
}

// The ProgressData struct represents new progress information on an operation
type ProgressData struct {
	// Preferred string repreentation of progress (always set)
	Text string

	// Progress in percent
	Percentage int

	// Number of bytes transferred (for files)
	TransferredBytes int64

	// Total number of bytes (for files)
	TotalBytes int64
}

// The ImageCreateArgs struct is used for direct image upload
type ImageCreateArgs struct {
	// Reader for the meta file
	MetaFile io.Reader

	// Filename for the meta file
	MetaName string

	// Reader for the rootfs file
	RootfsFile io.Reader

	// Filename for the rootfs file
	RootfsName string

	// Progress handler (called with upload progress)
	ProgressHandler func(progress ProgressData)
}

// The ImageFileRequest struct is used for an image download request
type ImageFileRequest struct {
	// Writer for the metadata file
	MetaFile io.WriteSeeker

	// Writer for the rootfs file
	RootfsFile io.WriteSeeker

	// Progress handler (called whenever some progress is made)
	ProgressHandler func(progress ProgressData)

	// A canceler that can be used to interrupt some part of the image download request
	Canceler *cancel.Canceler

	// Path retriever for image delta downloads
	// If set, it must return the path to the image file or an empty string if not available
	DeltaSourceRetriever func(fingerprint string, file string) string
}

// The ImageFileResponse struct is used as the response for image downloads
type ImageFileResponse struct {
	// Filename for the metadata file
	MetaName string

	// Size of the metadata file
	MetaSize int64

	// Filename for the rootfs file
	RootfsName string

	// Size of the rootfs file
	RootfsSize int64
}

// The ImageCopyArgs struct is used to pass additional options during image copy
type ImageCopyArgs struct {
	// Aliases to add to the copied image.
	Aliases []api.ImageAlias

	// Whether to have LXD keep this image up to date
	AutoUpdate bool

	// Whether to copy the source image aliases to the target
	CopyAliases bool

	// Whether this image is to be made available to unauthenticated users
	Public bool
}

// The ContainerCopyArgs struct is used to pass additional options during container copy
type ContainerCopyArgs struct {
	// If set, the container will be renamed on copy
	Name string

	// If set, the container running state will be transferred (live migration)
	Live bool

	// If set, only the container will copied, its snapshots won't
	ContainerOnly bool

	// The transfer mode, can be "pull" (default), "push" or "relay"
	Mode string
}

// The ContainerSnapshotCopyArgs struct is used to pass additional options during container copy
type ContainerSnapshotCopyArgs struct {
	// If set, the container will be renamed on copy
	Name string

	// The transfer mode, can be "pull" (default), "push" or "relay"
	Mode string

	// API extension: container_snapshot_stateful_migration
	// If set, the container running state will be transferred (live migration)
	Live bool
}

// The ContainerExecArgs struct is used to pass additional options during container exec
type ContainerExecArgs struct {
	// Standard input
	Stdin io.ReadCloser

	// Standard output
	Stdout io.WriteCloser

	// Standard error
	Stderr io.WriteCloser

	// Control message handler (window resize, signals, ...)
	Control func(conn *websocket.Conn)

	// Channel that will be closed when all data operations are done
	DataDone chan bool
}

// The ContainerFileArgs struct is used to pass the various options for a container file upload
type ContainerFileArgs struct {
	// File content
	Content io.ReadSeeker

	// User id that owns the file
	UID int64

	// Group id that owns the file
	GID int64

	// File permissions
	Mode int

	// File type (file or directory)
	Type string

	// File write mode (overwrite or append)
	WriteMode string
}

// The ContainerFileResponse struct is used as part of the response for a container file download
type ContainerFileResponse struct {
	// User id that owns the file
	UID int64

	// Group id that owns the file
	GID int64

	// File permissions
	Mode int

	// File type (file or directory)
	Type string

	// If a directory, the list of files inside it
	Entries []string
}
<commit_msg>client: Name all the return values in interfaces<commit_after>package lxd

import (
	"io"
	"net/http"

	"github.com/gorilla/websocket"

	"github.com/lxc/lxd/shared/api"
	"github.com/lxc/lxd/shared/cancel"
)

// The Server type represents a generic read-only server.
type Server interface {
	GetConnectionInfo() (info *ConnectionInfo, err error)
	GetHTTPClient() (client *http.Client, err error)
}

// The ImageServer type represents a read-only image server.
type ImageServer interface {
	Server

	// Image handling functions
	GetImages() (images []api.Image, err error)
	GetImageFingerprints() (fingerprints []string, err error)

	GetImage(fingerprint string) (image *api.Image, ETag string, err error)
	GetImageFile(fingerprint string, req ImageFileRequest) (resp *ImageFileResponse, err error)
	GetImageSecret(fingerprint string) (secret string, err error)

	GetPrivateImage(fingerprint string, secret string) (image *api.Image, ETag string, err error)
	GetPrivateImageFile(fingerprint string, secret string, req ImageFileRequest) (resp *ImageFileResponse, err error)

	GetImageAliases() (aliases []api.ImageAliasesEntry, err error)
	GetImageAliasNames() (names []string, err error)

	GetImageAlias(name string) (alias *api.ImageAliasesEntry, ETag string, err error)
}

// The ContainerServer type represents a full featured LXD server.
type ContainerServer interface {
	ImageServer

	// Server functions
	GetServer() (server *api.Server, ETag string, err error)
	GetServerResources() (resources *api.Resources, err error)
	UpdateServer(server api.ServerPut, ETag string) (err error)
	HasExtension(extension string) (exists bool)
	RequireAuthenticated(authenticated bool)

	// Certificate functions
	GetCertificateFingerprints() (fingerprints []string, err error)
	GetCertificates() (certificates []api.Certificate, err error)
	GetCertificate(fingerprint string) (certificate *api.Certificate, ETag string, err error)
	CreateCertificate(certificate api.CertificatesPost) (err error)
	UpdateCertificate(fingerprint string, certificate api.CertificatePut, ETag string) (err error)
	DeleteCertificate(fingerprint string) (err error)

	// Container functions
	GetContainerNames() (names []string, err error)
	GetContainers() (containers []api.Container, err error)
	GetContainer(name string) (container *api.Container, ETag string, err error)
	CreateContainer(container api.ContainersPost) (op *Operation, err error)
	CreateContainerFromImage(source ImageServer, image api.Image, imgcontainer api.ContainersPost) (op *RemoteOperation, err error)
	CopyContainer(source ContainerServer, container api.Container, args *ContainerCopyArgs) (op *RemoteOperation, err error)
	UpdateContainer(name string, container api.ContainerPut, ETag string) (op *Operation, err error)
	RenameContainer(name string, container api.ContainerPost) (op *Operation, err error)
	MigrateContainer(name string, container api.ContainerPost) (op *Operation, err error)
	DeleteContainer(name string) (op *Operation, err error)

	ExecContainer(containerName string, exec api.ContainerExecPost, args *ContainerExecArgs) (op *Operation, err error)

	GetContainerFile(containerName string, path string) (content io.ReadCloser, resp *ContainerFileResponse, err error)
	CreateContainerFile(containerName string, path string, args ContainerFileArgs) (err error)
	DeleteContainerFile(containerName string, path string) (err error)

	GetContainerSnapshotNames(containerName string) (names []string, err error)
	GetContainerSnapshots(containerName string) (snapshots []api.ContainerSnapshot, err error)
	GetContainerSnapshot(containerName string, name string) (snapshot *api.ContainerSnapshot, ETag string, err error)
	CreateContainerSnapshot(containerName string, snapshot api.ContainerSnapshotsPost) (op *Operation, err error)
	CopyContainerSnapshot(source ContainerServer, snapshot api.ContainerSnapshot, args *ContainerSnapshotCopyArgs) (op *RemoteOperation, err error)
	RenameContainerSnapshot(containerName string, name string, container api.ContainerSnapshotPost) (op *Operation, err error)
	MigrateContainerSnapshot(containerName string, name string, container api.ContainerSnapshotPost) (op *Operation, err error)
	DeleteContainerSnapshot(containerName string, name string) (op *Operation, err error)

	GetContainerState(name string) (state *api.ContainerState, ETag string, err error)
	UpdateContainerState(name string, state api.ContainerStatePut, ETag string) (op *Operation, err error)

	GetContainerLogfiles(name string) (logfiles []string, err error)
	GetContainerLogfile(name string, filename string) (content io.ReadCloser, err error)
	DeleteContainerLogfile(name string, filename string) (err error)

	GetContainerMetadata(name string) (metadata *api.ImageMetadata, ETag string, err error)
	SetContainerMetadata(name string, metadata api.ImageMetadata, ETag string) (err error)

	GetContainerTemplateFiles(containerName string) (templates []string, err error)
	GetContainerTemplateFile(containerName string, templateName string) (content io.ReadCloser, err error)
	CreateContainerTemplateFile(containerName string, templateName string, content io.ReadSeeker) (err error)
	UpdateContainerTemplateFile(containerName string, templateName string, content io.ReadSeeker) (err error)
	DeleteContainerTemplateFile(name string, templateName string) (err error)

	// Event handling functions
	GetEvents() (listener *EventListener, err error)

	// Image functions
	CreateImage(image api.ImagesPost, args *ImageCreateArgs) (op *Operation, err error)
	CopyImage(source ImageServer, image api.Image, args *ImageCopyArgs) (op *RemoteOperation, err error)
	UpdateImage(fingerprint string, image api.ImagePut, ETag string) (err error)
	DeleteImage(fingerprint string) (op *Operation, err error)
	RefreshImage(fingerprint string) (op *Operation, err error)
	CreateImageSecret(fingerprint string) (op *Operation, err error)
	CreateImageAlias(alias api.ImageAliasesPost) (err error)
	UpdateImageAlias(name string, alias api.ImageAliasesEntryPut, ETag string) (err error)
	RenameImageAlias(name string, alias api.ImageAliasesEntryPost) (err error)
	DeleteImageAlias(name string) (err error)

	// Network functions ("network" API extension)
	GetNetworkNames() (names []string, err error)
	GetNetworks() (networks []api.Network, err error)
	GetNetwork(name string) (network *api.Network, ETag string, err error)
	CreateNetwork(network api.NetworksPost) (err error)
	UpdateNetwork(name string, network api.NetworkPut, ETag string) (err error)
	RenameNetwork(name string, network api.NetworkPost) (err error)
	DeleteNetwork(name string) (err error)

	// Operation functions
	GetOperationUUIDs() (uuids []string, err error)
	GetOperations() (operations []api.Operation, err error)
	GetOperation(uuid string) (op *api.Operation, ETag string, err error)
	DeleteOperation(uuid string) (err error)
	GetOperationWebsocket(uuid string, secret string) (conn *websocket.Conn, err error)

	// Profile functions
	GetProfileNames() (names []string, err error)
	GetProfiles() (profiles []api.Profile, err error)
	GetProfile(name string) (profile *api.Profile, ETag string, err error)
	CreateProfile(profile api.ProfilesPost) (err error)
	UpdateProfile(name string, profile api.ProfilePut, ETag string) (err error)
	RenameProfile(name string, profile api.ProfilePost) (err error)
	DeleteProfile(name string) (err error)

	// Storage pool functions ("storage" API extension)
	GetStoragePoolNames() (names []string, err error)
	GetStoragePools() (pools []api.StoragePool, err error)
	GetStoragePool(name string) (pool *api.StoragePool, ETag string, err error)
	GetStoragePoolResources(name string) (resources *api.ResourcesStoragePool, err error)
	CreateStoragePool(pool api.StoragePoolsPost) (err error)
	UpdateStoragePool(name string, pool api.StoragePoolPut, ETag string) (err error)
	DeleteStoragePool(name string) (err error)

	// Storage volume functions ("storage" API extension)
	GetStoragePoolVolumeNames(pool string) (names []string, err error)
	GetStoragePoolVolumes(pool string) (volumes []api.StorageVolume, err error)
	GetStoragePoolVolume(pool string, volType string, name string) (volume *api.StorageVolume, ETag string, err error)
	CreateStoragePoolVolume(pool string, volume api.StorageVolumesPost) (err error)
	UpdateStoragePoolVolume(pool string, volType string, name string, volume api.StorageVolumePut, ETag string) (err error)
	DeleteStoragePoolVolume(pool string, volType string, name string) (err error)
	RenameStoragePoolVolume(pool string, volType string, name string, volume api.StorageVolumePost) (err error)

	// Internal functions (for internal use)
	RawQuery(method string, path string, data interface{}, queryETag string) (resp *api.Response, ETag string, err error)
	RawWebsocket(path string) (conn *websocket.Conn, err error)
}

// The ConnectionInfo struct represents general information for a connection
type ConnectionInfo struct {
	Addresses   []string
	Certificate string
	Protocol    string
}

// The ProgressData struct represents new progress information on an operation
type ProgressData struct {
	// Preferred string repreentation of progress (always set)
	Text string

	// Progress in percent
	Percentage int

	// Number of bytes transferred (for files)
	TransferredBytes int64

	// Total number of bytes (for files)
	TotalBytes int64
}

// The ImageCreateArgs struct is used for direct image upload
type ImageCreateArgs struct {
	// Reader for the meta file
	MetaFile io.Reader

	// Filename for the meta file
	MetaName string

	// Reader for the rootfs file
	RootfsFile io.Reader

	// Filename for the rootfs file
	RootfsName string

	// Progress handler (called with upload progress)
	ProgressHandler func(progress ProgressData)
}

// The ImageFileRequest struct is used for an image download request
type ImageFileRequest struct {
	// Writer for the metadata file
	MetaFile io.WriteSeeker

	// Writer for the rootfs file
	RootfsFile io.WriteSeeker

	// Progress handler (called whenever some progress is made)
	ProgressHandler func(progress ProgressData)

	// A canceler that can be used to interrupt some part of the image download request
	Canceler *cancel.Canceler

	// Path retriever for image delta downloads
	// If set, it must return the path to the image file or an empty string if not available
	DeltaSourceRetriever func(fingerprint string, file string) string
}

// The ImageFileResponse struct is used as the response for image downloads
type ImageFileResponse struct {
	// Filename for the metadata file
	MetaName string

	// Size of the metadata file
	MetaSize int64

	// Filename for the rootfs file
	RootfsName string

	// Size of the rootfs file
	RootfsSize int64
}

// The ImageCopyArgs struct is used to pass additional options during image copy
type ImageCopyArgs struct {
	// Aliases to add to the copied image.
	Aliases []api.ImageAlias

	// Whether to have LXD keep this image up to date
	AutoUpdate bool

	// Whether to copy the source image aliases to the target
	CopyAliases bool

	// Whether this image is to be made available to unauthenticated users
	Public bool
}

// The ContainerCopyArgs struct is used to pass additional options during container copy
type ContainerCopyArgs struct {
	// If set, the container will be renamed on copy
	Name string

	// If set, the container running state will be transferred (live migration)
	Live bool

	// If set, only the container will copied, its snapshots won't
	ContainerOnly bool

	// The transfer mode, can be "pull" (default), "push" or "relay"
	Mode string
}

// The ContainerSnapshotCopyArgs struct is used to pass additional options during container copy
type ContainerSnapshotCopyArgs struct {
	// If set, the container will be renamed on copy
	Name string

	// The transfer mode, can be "pull" (default), "push" or "relay"
	Mode string

	// API extension: container_snapshot_stateful_migration
	// If set, the container running state will be transferred (live migration)
	Live bool
}

// The ContainerExecArgs struct is used to pass additional options during container exec
type ContainerExecArgs struct {
	// Standard input
	Stdin io.ReadCloser

	// Standard output
	Stdout io.WriteCloser

	// Standard error
	Stderr io.WriteCloser

	// Control message handler (window resize, signals, ...)
	Control func(conn *websocket.Conn)

	// Channel that will be closed when all data operations are done
	DataDone chan bool
}

// The ContainerFileArgs struct is used to pass the various options for a container file upload
type ContainerFileArgs struct {
	// File content
	Content io.ReadSeeker

	// User id that owns the file
	UID int64

	// Group id that owns the file
	GID int64

	// File permissions
	Mode int

	// File type (file or directory)
	Type string

	// File write mode (overwrite or append)
	WriteMode string
}

// The ContainerFileResponse struct is used as part of the response for a container file download
type ContainerFileResponse struct {
	// User id that owns the file
	UID int64

	// Group id that owns the file
	GID int64

	// File permissions
	Mode int

	// File type (file or directory)
	Type string

	// If a directory, the list of files inside it
	Entries []string
}
<|endoftext|>


<commit_before>package main

import (
    "os"
    "os/signal"
    "syscall"
    "bufio"
    "fmt"
    "log"
    //"bytes"
    //"encoding/binary"
    "strings"
    "path/filepath"
    "net"
    "mob/proto"
    "mob/client/music"
    //"github.com/tcolgate/mp3"
    "github.com/cenkalti/rpc2"
    "time"
    "sync"
)

// TCP and RPC handlers for the tracker
var conn net.Conn
var client *rpc2.Client

// UDP handler for handshake packets
var packetConn net.PacketConn

// This client's LAN IP address
// TODO: this is a cmd line arg; want to discover this automatically
var publicIp string

// Control flags
var connectedToTracker bool // did join a tracker
var isSeeder bool           // tells us if we have access to the mp3 or not
var alreadySeeding bool     // prevent tracker rpc from being over called

// Seeder's data structures
var peerToConn map[string]net.Conn
var seedees []string

var maxSeedees int

// Assume mp3 is no larger than 50MB. We reuse this buffer for each song we play.
// Don't need to worry when it gets GCed since we're using it the whole time
var songBuf [20 * 1024 * 1024]byte

// TODO: clients make rpc to say that they are done with the song
func main() {
    // Handle kill signal gracefully
    c := make(chan os.Signal, 2)
    signal.Notify(c, os.Interrupt, syscall.SIGTERM)
    go func() {
        <-c
        if client != nil {
            handleLeave()
        }
        os.Exit(1)
    }()

    // Initialize SDL audio
    music.Init()
    defer music.Quit()

    // Get our local network IP address
    publicIp = os.Args[1]

    // Set max number of seedees to our stream to prevent congestion on peers
    maxSeedees = 1
    seedees = make([]string, 0)

    connectedToTracker = false
    isSeeder = false
    alreadySeeding = false

    // Start the shell
    fmt.Print(
`
              ___.
  _____   ____\_ |__
 /     \ /  _ \| __ \
|  Y Y  (  <_> ) \_\ \
|__|_|  /\____/|___  /
      \/           \/
`)

    fmt.Println()
    fmt.Println("internet radio version 0.0.0")
    reader := bufio.NewReader(os.Stdin)

    for {
        fmt.Print(">>> ")
        input, _ := reader.ReadString('\n')
        input = strings.TrimSuffix(input, "\n")
        input = strings.TrimSpace(input)

        strs := strings.Split(input, " ")
        switch strs[0] {
        case "join": // join 192.168.1.12:1234
            handleJoin(strs[1])
        case "leave": // leave the network
            handleLeave()
        case "list-songs": // list
            handleListSongs()
        case "list-peers":
            handleListPeers()
        case "play": // play blah.mp3
            handlePlay(strs[1])
        case "quit": // quit the program
            if connectedToTracker {
                handleLeave()
            }
            return
        case "help": // help
            handleHelp()
        default:     // error message continue
            fmt.Println("Error: not a valid command")
            //handleHelp()
        }
    }
}

func handleJoin(input string) {
    if connectedToTracker {
        handleLeave()
    }

    var err error
    conn, err = net.Dial("tcp", input)
    if err != nil {
        log.Println(err)
    }

    client = rpc2.NewClient(conn)

    // Register the rpc handlers for seedToPeers() so that tracker can notify
    // client when to start seeding
    client.Handle("seed", func(client *rpc2.Client, args *proto.TrackerRes, reply *proto.HandshakePacket) error {
        if (alreadySeeding) {
          return nil
        }

        // Start seeding
        alreadySeeding = true
        isSeeder = true
        // TODO: set alreadyseeding to false once a done rpc is issued to the tracker
        go seedToPeers(args.Res)
        return nil
    })

    client.Handle("start-playing", func(client *rpc2.Client, args *proto.ClientInfoMsg, reply *proto.TrackerRes) error {
        // TODO: rpc for client to say song is done playing
        // currSong = ""
        return nil
    })

    go client.Run()

    connectedToTracker = true
    fmt.Println("Begin listen for peers")
    go listenForPeers()
    go handlePing()

    _, port, _ := net.SplitHostPort(conn.LocalAddr().String())
    client.Call("join", proto.ClientInfoMsg{net.JoinHostPort(publicIp, port), getSongNames()}, nil)
}

func handleLeave() {
    if !connectedToTracker {
        fmt.Println("Error: not connected to a tracker")
        return
    }

    connectedToTracker = false
    client.Call("leave", proto.ClientInfoMsg{conn.LocalAddr().String(), nil}, nil)
    fmt.Println("Leaving the tracker in 5 sec ...")
    time.Sleep(5000 * time.Millisecond)
    packetConn.Close()
    fmt.Println("done")
    //conn.Close() // TODO: is this right?
}

func handleListSongs() {
    if !connectedToTracker {
        fmt.Println("Error: not connected to a tracker")
        return
    }

    var res proto.TrackerSlice
    client.Call("list-songs", proto.ClientCmdMsg{""}, &res)
    fmt.Println(res.Res)
}

func handleListPeers() {
    if !connectedToTracker {
        fmt.Println("Error: not connected to a tracker")
        return
    }

    var res proto.TrackerSlice
    client.Call("list-peers", proto.ClientCmdMsg{""}, &res)
    fmt.Println(res.Res)
}

// need to listen for when to start playing to others
func handlePlay(input string) {
    if !connectedToTracker {
        fmt.Println("Error: not connected to a tracker")
        return
    }

    client.Call("play", proto.ClientCmdMsg{input}, nil)
}

func handleHelp() {
    fmt.Print(
`commands:
    join  - connect to a tracker
    leave - disconnect from a tracker
    list-songs - list all available songs
    list-peers - list all peers on the network
    play - enqueue a song to be played
    help - show commands
    quit - exit the program
`)
}

func handlePing() {
    _, port, _ := net.SplitHostPort(conn.LocalAddr().String())
    for {
        client.Call("ping", proto.ClientInfoMsg{net.JoinHostPort(publicIp, port), nil}, nil)
        time.Sleep(500 * time.Millisecond)
    }
}

// TODO: when song is done playing, set songBuf = songBuf[:0] to clear it
func listenForMp3Frames(publicIp string) {
    //mp3FrameAddr  := net.UDPAddr{IP: net.ParseIP(publicIp), Port: 6122,}
    //udpMp3Framer, _  = net.ListenUDP("udp", &mp3FrameAddr)

    // TODO: add second level seeding!

    //for {
    //    n, addr, err := udpHandshaker.ReadFromUDP(songBuf)
    //     go seedToPeers(currSong)
    //}
}

// Listens for udp request packets from peers in order to build the stream graph
// Handles all possible handshake packets that will be sent to this peer.
// Called in handleJoin when you join a tracker
func listenForPeers() {
    // listen to incoming udp packets
    fmt.Println("listening for peers!")
    var err error
    packetConn, err = net.ListenPacket("udp", net.JoinHostPort(publicIp, "6121"))
    if err != nil {
        log.Fatal(err)
    }
    //defer pc.Close()

    // Continously listen for handshake packets
    // Eventually after a successful round of handshaking, all peers will
    // be seeders and will block on the next ReadFrom() call until the next
    // round
    for connectedToTracker { // terminate when we leave a tracker
        // Read a packet
        buffer := make([]byte, 2048)
        n, addr, e := packetConn.ReadFrom(buffer) // block here
        if e != nil {
            log.Fatal("error when reading packet")
            return
        }
        s := string(buffer[:n])
        substrs := strings.Split(s, ":")
        ip, port, _ := net.SplitHostPort(addr.String())
        //dest := net.JoinHostPort(ip, "6121")
        fmt.Println(ip)
        fmt.Println(port)
        fmt.Println(s)
        //raddr := net.UDPAddr{IP: net.ParseIP(ip), Port: 6121}

        // Process the packet and handle
        switch substrs[0] {
        case "request": // where this client is a non-seeder
            if isSeeder || hasSongLocally(substrs[1]) {
                go func() {
                    for i := 0; i < 10; i++ { // redundancy
                        packetConn.WriteTo([]byte("reject"), addr)
                        time.Sleep(500 * time.Microsecond)
                    }
                }()
            } else {
                go func() {
                    for i := 0; i < 10; i++ { // redundancy
                        packetConn.WriteTo([]byte("accept"), addr)
                        time.Sleep(500 * time.Microsecond)
                    }
                }()
            }
        case "confirm": // where this client is a non-seeder
            if isSeeder {
                go func() {
                    for i := 0; i < 10; i++ { // redundancy
                        packetConn.WriteTo([]byte("reject"), addr)
                        time.Sleep(500 * time.Microsecond)
                    }
                }()
            } else {
                // Set this to false after the song finishes playing
                isSeeder = true
                go seedToPeers(substrs[1])
            }
        case "accept": // where this client is a seeder
            if isSeeder && len(seedees) < maxSeedees {
                ip, _, _ := net.SplitHostPort(addr.String())
                seedees = append(seedees, ip)
                go func() {
                    for i := 0; i < 10; i++ { // redundancy
                        packetConn.WriteTo([]byte("confirm"), addr)
                        time.Sleep(500 * time.Microsecond)
                    }
                }()
                peerToConn[ip].Close()
                peerToConn[ip] = nil
            } else if isSeeder && len(seedees) >= maxSeedees {
                peerToConn[ip].Close()
                peerToConn[ip] = nil
            } else {
                // is a non-seeder; shouldn't get here
                log.Fatal("non-seeder tried to accept other non-seeder")
            }
        case "reject": // where this client is a seeder
            // Clean up our connection to this peer
            peerToConn[ip].Close()
            peerToConn[ip] = nil
        }
    }
}

// If this client has access to mp3 stream, find peers to stream to.
// Broadcasts packets to peers until every peer has responded.
// Called by tracker rpc.
func seedToPeers(songFile string) {
    var wg sync.WaitGroup

    // Get list of peers from tracker
    var peers proto.TrackerSlice
    client.Call("list-peers", proto.ClientCmdMsg{""}, &peers)

    peerToConn = make(map[string]net.Conn)
    seedees = make([]string, 0)

    // Loop to acquire udp connections to all other peers
    for _, peer := range peers.Res {
        ip, _, _ := net.SplitHostPort(peer)

        if ip != publicIp { // check not this client
            // Connect to an available peer
            pc, _ := net.Dial("udp", net.JoinHostPort(ip, "6121"))
            fmt.Println("contacting " + net.JoinHostPort(ip, "6121") + " ...")
            peerToConn[ip] = pc

            wg.Add(1)
            // ARQ requests to the peer until we set its net.Conn to nil
            go func() {
                defer wg.Done()
                for peerToConn[peer] != nil {
                    pc.Write([]byte("request:" + songFile))
                    time.Sleep(500 * time.Microsecond)
                }
            }()
        }
    }

    wg.Wait()

    // Done our job in stream graph; make rpc to tracker?
    // Or have ping have us move forward
    // TODO:
    fmt.Println("This client is ready to play!")
}

func handleStartPlaying() {
    // tracker will call this client rpc on ping when all peers are ready to
    // play
    //
    //
}

func handleDonePlaying() {
    // reset globals

    // make rpc call to tracker
}

// Returns csv of all song names in the songs folder.
func getSongNames() ([]string) {
    var songs []string
    filepath.Walk("../songs", func (p string, i os.FileInfo, err error) error {
        if err != nil {
            log.Println(err)
            return nil
        }

        s := filepath.Base(p)
        if strings.Compare(s, "songs") != 0 && strings.Contains(s, ".mp3") {
            songs = append(songs, s)
        }

        return nil
    })

    return songs
}

func hasSongLocally(songFile string) bool {
    for _, song := range getSongNames() {
        if song == songFile {
            return true
        }
    }

    return false
}
<commit_msg>was using wrong port on responses<commit_after>package main

import (
    "os"
    "os/signal"
    "syscall"
    "bufio"
    "fmt"
    "log"
    //"bytes"
    //"encoding/binary"
    "strings"
    "path/filepath"
    "net"
    "mob/proto"
    "mob/client/music"
    //"github.com/tcolgate/mp3"
    "github.com/cenkalti/rpc2"
    "time"
    "sync"
)

// TCP and RPC handlers for the tracker
var conn net.Conn
var client *rpc2.Client

// UDP handler for handshake packets
var packetConn net.PacketConn

// This client's LAN IP address
// TODO: this is a cmd line arg; want to discover this automatically
var publicIp string

// Control flags
var connectedToTracker bool // did join a tracker
var isSeeder bool           // tells us if we have access to the mp3 or not
var alreadySeeding bool     // prevent tracker rpc from being over called

// Seeder's data structures
var peerToConn map[string]net.Conn
var seedees []string

var maxSeedees int

// Assume mp3 is no larger than 50MB. We reuse this buffer for each song we play.
// Don't need to worry when it gets GCed since we're using it the whole time
var songBuf [20 * 1024 * 1024]byte

// TODO: clients make rpc to say that they are done with the song
func main() {
    // Handle kill signal gracefully
    c := make(chan os.Signal, 2)
    signal.Notify(c, os.Interrupt, syscall.SIGTERM)
    go func() {
        <-c
        if client != nil {
            handleLeave()
        }
        os.Exit(1)
    }()

    // Initialize SDL audio
    music.Init()
    defer music.Quit()

    // Get our local network IP address
    publicIp = os.Args[1]

    // Set max number of seedees to our stream to prevent congestion on peers
    maxSeedees = 1
    seedees = make([]string, 0)
    peerToConn = make(map[string]net.Conn)
    connectedToTracker = false
    isSeeder = false
    alreadySeeding = false

    // Start the shell
    fmt.Print(
`
              ___.
  _____   ____\_ |__
 /     \ /  _ \| __ \
|  Y Y  (  <_> ) \_\ \
|__|_|  /\____/|___  /
      \/           \/
`)

    fmt.Println()
    fmt.Println("internet radio version 0.0.0")
    reader := bufio.NewReader(os.Stdin)

    for {
        fmt.Print(">>> ")
        input, _ := reader.ReadString('\n')
        input = strings.TrimSuffix(input, "\n")
        input = strings.TrimSpace(input)

        strs := strings.Split(input, " ")
        switch strs[0] {
        case "join": // join 192.168.1.12:1234
            handleJoin(strs[1])
        case "leave": // leave the network
            handleLeave()
        case "list-songs": // list
            handleListSongs()
        case "list-peers":
            handleListPeers()
        case "play": // play blah.mp3
            handlePlay(strs[1])
        case "quit": // quit the program
            if connectedToTracker {
                handleLeave()
            }
            return
        case "help": // help
            handleHelp()
        default:     // error message continue
            fmt.Println("Error: not a valid command")
            //handleHelp()
        }
    }
}

func handleJoin(input string) {
    if connectedToTracker {
        handleLeave()
    }

    var err error
    conn, err = net.Dial("tcp", input)
    if err != nil {
        log.Println(err)
    }

    client = rpc2.NewClient(conn)

    // Register the rpc handlers for seedToPeers() so that tracker can notify
    // client when to start seeding
    client.Handle("seed", func(client *rpc2.Client, args *proto.TrackerRes, reply *proto.HandshakePacket) error {
        if (alreadySeeding) {
          return nil
        }

        // Start seeding
        alreadySeeding = true
        isSeeder = true
        // TODO: set alreadyseeding to false once a done rpc is issued to the tracker
        go seedToPeers(args.Res)
        return nil
    })

    client.Handle("start-playing", func(client *rpc2.Client, args *proto.ClientInfoMsg, reply *proto.TrackerRes) error {
        // TODO: rpc for client to say song is done playing
        // currSong = ""
        return nil
    })

    go client.Run()

    connectedToTracker = true
    fmt.Println("Begin listen for peers")
    go listenForPeers()
    go handlePing()

    _, port, _ := net.SplitHostPort(conn.LocalAddr().String())
    client.Call("join", proto.ClientInfoMsg{net.JoinHostPort(publicIp, port), getSongNames()}, nil)
}

func handleLeave() {
    if !connectedToTracker {
        fmt.Println("Error: not connected to a tracker")
        return
    }

    connectedToTracker = false
    client.Call("leave", proto.ClientInfoMsg{conn.LocalAddr().String(), nil}, nil)
    fmt.Println("Leaving the tracker in 5 sec ...")
    time.Sleep(5000 * time.Millisecond)
    packetConn.Close()
    fmt.Println("done")
    //conn.Close() // TODO: is this right?
}

func handleListSongs() {
    if !connectedToTracker {
        fmt.Println("Error: not connected to a tracker")
        return
    }

    var res proto.TrackerSlice
    client.Call("list-songs", proto.ClientCmdMsg{""}, &res)
    fmt.Println(res.Res)
}

func handleListPeers() {
    if !connectedToTracker {
        fmt.Println("Error: not connected to a tracker")
        return
    }

    var res proto.TrackerSlice
    client.Call("list-peers", proto.ClientCmdMsg{""}, &res)
    fmt.Println(res.Res)
}

// need to listen for when to start playing to others
func handlePlay(input string) {
    if !connectedToTracker {
        fmt.Println("Error: not connected to a tracker")
        return
    }

    client.Call("play", proto.ClientCmdMsg{input}, nil)
}

func handleHelp() {
    fmt.Print(
`commands:
    join  - connect to a tracker
    leave - disconnect from a tracker
    list-songs - list all available songs
    list-peers - list all peers on the network
    play - enqueue a song to be played
    help - show commands
    quit - exit the program
`)
}

func handlePing() {
    _, port, _ := net.SplitHostPort(conn.LocalAddr().String())
    for {
        client.Call("ping", proto.ClientInfoMsg{net.JoinHostPort(publicIp, port), nil}, nil)
        time.Sleep(500 * time.Millisecond)
    }
}

// TODO: when song is done playing, set songBuf = songBuf[:0] to clear it
func listenForMp3Frames(publicIp string) {
    //mp3FrameAddr  := net.UDPAddr{IP: net.ParseIP(publicIp), Port: 6122,}
    //udpMp3Framer, _  = net.ListenUDP("udp", &mp3FrameAddr)

    // TODO: add second level seeding!

    //for {
    //    n, addr, err := udpHandshaker.ReadFromUDP(songBuf)
    //     go seedToPeers(currSong)
    //}
}

// Listens for udp request packets from peers in order to build the stream graph
// Handles all possible handshake packets that will be sent to this peer.
// Called in handleJoin when you join a tracker
func listenForPeers() {
    // listen to incoming udp packets
    fmt.Println("listening for peers!")
    var err error
    packetConn, err = net.ListenPacket("udp", net.JoinHostPort(publicIp, "6121"))
    if err != nil {
        log.Fatal(err)
    }
    //defer pc.Close()

    // Continously listen for handshake packets
    // Eventually after a successful round of handshaking, all peers will
    // be seeders and will block on the next ReadFrom() call until the next
    // round
    for connectedToTracker { // terminate when we leave a tracker
        // Read a packet
        buffer := make([]byte, 2048)
        n, addr, e := packetConn.ReadFrom(buffer) // block here
        if e != nil {
            log.Fatal("error when reading packet")
            return
        }
        s := string(buffer[:n])
        substrs := strings.Split(s, ":")
        ip, port, _ := net.SplitHostPort(addr.String())
        //dest := net.JoinHostPort(ip, "6121")
        fmt.Println(ip)
        fmt.Println(port)
        fmt.Println(s)
        //raddr := net.UDPAddr{IP: net.ParseIP(ip), Port: 6121}

        // Process the packet and handle
        switch substrs[0] {
        case "request": // where this client is a non-seeder
            if isSeeder || hasSongLocally(substrs[1]) {
                go func() {
                    for i := 0; i < 10; i++ { // redundancy
                        packetConn.WriteTo([]byte("reject"), addr)
                        time.Sleep(500 * time.Microsecond)
                    }
                }()
            } else {
                go func() {
                    for i := 0; i < 10; i++ { // redundancy
                        packetConn.WriteTo([]byte("accept"), addr)
                        time.Sleep(500 * time.Microsecond)
                    }
                }()
            }
        case "confirm": // where this client is a non-seeder
            if isSeeder {
                go func() {
                    for i := 0; i < 10; i++ { // redundancy
                        packetConn.WriteTo([]byte("reject"), addr)
                        time.Sleep(500 * time.Microsecond)
                    }
                }()
            } else {
                // Set this to false after the song finishes playing
                isSeeder = true
                go seedToPeers(substrs[1])
            }
        case "accept": // where this client is a seeder
            if isSeeder && len(seedees) < maxSeedees {
                ip, _, _ := net.SplitHostPort(addr.String())
                seedees = append(seedees, ip)
                go func() {
                    for i := 0; i < 10; i++ { // redundancy
                        packetConn.WriteTo([]byte("confirm"), addr)
                        time.Sleep(500 * time.Microsecond)
                    }
                }()
                peerToConn[ip].Close()
                peerToConn[ip] = nil
            } else if isSeeder && len(seedees) >= maxSeedees {
                peerToConn[ip].Close()
                peerToConn[ip] = nil
            } else {
                // is a non-seeder; shouldn't get here
                log.Fatal("non-seeder tried to accept other non-seeder")
            }
        case "reject": // where this client is a seeder
            // Clean up our connection to this peer
            peerToConn[ip].Close()
            peerToConn[ip] = nil
        }
    }
}

// If this client has access to mp3 stream, find peers to stream to.
// Broadcasts packets to peers until every peer has responded.
// Called by tracker rpc.
func seedToPeers(songFile string) {
    var wg sync.WaitGroup

    // Get list of peers from tracker
    var peers proto.TrackerSlice
    client.Call("list-peers", proto.ClientCmdMsg{""}, &peers)

    peerToConn = make(map[string]net.Conn)
    seedees = make([]string, 0)

    // Loop to acquire udp connections to all other peers
    for _, peer := range peers.Res {
        ip, _, _ := net.SplitHostPort(peer)

        if ip != publicIp { // check not this client
            // Connect to an available peer
            laddr := net.UDPAddr{IP: net.ParseIP(publicIp), Port: 6121}
	        raddr := net.UDPAddr{IP: net.ParseIP(ip), Port: 6121}

	        pc, _ := net.DialUDP("udp", &laddr, &raddr)

            //pc, _ := net.Dial("udp", net.JoinHostPort(ip, "6121"))
            fmt.Println("contacting " + net.JoinHostPort(ip, "6121") + " ...")
            peerToConn[ip] = pc

            wg.Add(1)
            // ARQ requests to the peer until we set its net.Conn to nil
            go func() {
                defer wg.Done()
                for peerToConn[ip] != nil {
                    pc.Write([]byte("request:" + songFile))
                    time.Sleep(500 * time.Microsecond)
                }
            }()
        }
    }

    wg.Wait()
    fmt.Println(seedees)
    // Done our job in stream graph; make rpc to tracker?
    // Or have ping have us move forward
    // TODO:
    fmt.Println("This client is ready to play!")
}

func handleStartPlaying() {
    // tracker will call this client rpc on ping when all peers are ready to
    // play
    //
    //
}

func handleDonePlaying() {
    // reset globals

    // make rpc call to tracker
}

// Returns csv of all song names in the songs folder.
func getSongNames() ([]string) {
    var songs []string
    filepath.Walk("../songs", func (p string, i os.FileInfo, err error) error {
        if err != nil {
            log.Println(err)
            return nil
        }

        s := filepath.Base(p)
        if strings.Compare(s, "songs") != 0 && strings.Contains(s, ".mp3") {
            songs = append(songs, s)
        }

        return nil
    })

    return songs
}

func hasSongLocally(songFile string) bool {
    for _, song := range getSongNames() {
        if song == songFile {
            return true
        }
    }

    return false
}
<|endoftext|>


<commit_before>package client

import (
	"bytes"
	"fmt"
	"sync"
	"time"

	"github.com/micro/go-micro/broker"
	"github.com/micro/go-micro/codec"
	"github.com/micro/go-micro/errors"
	"github.com/micro/go-micro/metadata"
	"github.com/micro/go-micro/selector"
	"github.com/micro/go-micro/transport"

	"golang.org/x/net/context"
)

type rpcClient struct {
	once sync.Once
	opts Options
	pool *pool
}

func newRpcClient(opt ...Option) Client {
	opts := newOptions(opt...)

	rc := &rpcClient{
		once: sync.Once{},
		opts: opts,
		pool: newPool(opts.PoolSize, opts.PoolTTL),
	}

	c := Client(rc)

	// wrap in reverse
	for i := len(opts.Wrappers); i > 0; i-- {
		c = opts.Wrappers[i-1](c)
	}

	return c
}

func (r *rpcClient) newCodec(contentType string) (codec.NewCodec, error) {
	if c, ok := r.opts.Codecs[contentType]; ok {
		return c, nil
	}
	if cf, ok := defaultCodecs[contentType]; ok {
		return cf, nil
	}
	return nil, fmt.Errorf("Unsupported Content-Type: %s", contentType)
}

func (r *rpcClient) call(ctx context.Context, address string, req Request, resp interface{}, opts CallOptions) error {
	msg := &transport.Message{
		Header: make(map[string]string),
	}

	md, ok := metadata.FromContext(ctx)
	if ok {
		for k, v := range md {
			msg.Header[k] = v
		}
	}

	// set timeout in nanoseconds
	msg.Header["Timeout"] = fmt.Sprintf("%d", opts.RequestTimeout)
	// set the content type for the request
	msg.Header["Content-Type"] = req.ContentType()
	// set the accept header
	msg.Header["Accept"] = req.ContentType()

	cf, err := r.newCodec(req.ContentType())
	if err != nil {
		return errors.InternalServerError("go.micro.client", err.Error())
	}

	var grr error
	c, err := r.pool.getConn(address, r.opts.Transport, transport.WithTimeout(opts.DialTimeout))
	if err != nil {
		return errors.InternalServerError("go.micro.client", fmt.Sprintf("Error sending request: %v", err))
	}
	defer func() {
		// defer execution of release
		r.pool.release(address, c, grr)
	}()

	stream := &rpcStream{
		context: ctx,
		request: req,
		closed:  make(chan bool),
		codec:   newRpcPlusCodec(msg, c, cf),
	}
	defer stream.Close()

	ch := make(chan error, 1)

	go func() {
		defer func() {
			if r := recover(); r != nil {
				ch <- errors.InternalServerError("go.micro.client", "request error")
			}
		}()

		// send request
		if err := stream.Send(req.Request()); err != nil {
			ch <- err
			return
		}

		// recv request
		if err := stream.Recv(resp); err != nil {
			ch <- err
			return
		}

		// success
		ch <- nil
	}()

	select {
	case err := <-ch:
		grr = err
		return err
	case <-ctx.Done():
		grr = ctx.Err()
		return errors.New("go.micro.client", fmt.Sprintf("%v", ctx.Err()), 408)
	}
}

func (r *rpcClient) stream(ctx context.Context, address string, req Request, opts CallOptions) (Streamer, error) {
	msg := &transport.Message{
		Header: make(map[string]string),
	}

	md, ok := metadata.FromContext(ctx)
	if ok {
		for k, v := range md {
			msg.Header[k] = v
		}
	}

	// set timeout in nanoseconds
	msg.Header["Timeout"] = fmt.Sprintf("%d", opts.RequestTimeout)
	// set the content type for the request
	msg.Header["Content-Type"] = req.ContentType()
	// set the accept header
	msg.Header["Accept"] = req.ContentType()

	cf, err := r.newCodec(req.ContentType())
	if err != nil {
		return nil, errors.InternalServerError("go.micro.client", err.Error())
	}

	c, err := r.opts.Transport.Dial(address, transport.WithStream(), transport.WithTimeout(opts.DialTimeout))
	if err != nil {
		return nil, errors.InternalServerError("go.micro.client", fmt.Sprintf("Error sending request: %v", err))
	}

	stream := &rpcStream{
		context: ctx,
		request: req,
		closed:  make(chan bool),
		codec:   newRpcPlusCodec(msg, c, cf),
	}

	ch := make(chan error, 1)

	go func() {
		ch <- stream.Send(req.Request())
	}()

	var grr error

	select {
	case err := <-ch:
		grr = err
	case <-ctx.Done():
		grr = errors.New("go.micro.client", fmt.Sprintf("%v", ctx.Err()), 408)
	}

	if grr != nil {
		stream.Close()
		return nil, grr
	}

	return stream, nil
}

func (r *rpcClient) Init(opts ...Option) error {
	size := r.opts.PoolSize
	ttl := r.opts.PoolTTL

	for _, o := range opts {
		o(&r.opts)
	}

	// recreate the pool if the options changed
	if size != r.opts.PoolSize || ttl != r.opts.PoolTTL {
		r.pool = newPool(r.opts.PoolSize, r.opts.PoolTTL)
	}

	return nil
}

func (r *rpcClient) Options() Options {
	return r.opts
}

func (r *rpcClient) CallRemote(ctx context.Context, address string, request Request, response interface{}, opts ...CallOption) error {
	// make a copy of call opts
	callOpts := r.opts.CallOptions
	for _, opt := range opts {
		opt(&callOpts)
	}
	return r.call(ctx, address, request, response, callOpts)
}

func (r *rpcClient) Call(ctx context.Context, request Request, response interface{}, opts ...CallOption) error {
	// make a copy of call opts
	callOpts := r.opts.CallOptions
	for _, opt := range opts {
		opt(&callOpts)
	}

	// get next nodes from the selector
	next, err := r.opts.Selector.Select(request.Service(), callOpts.SelectOptions...)
	if err != nil && err == selector.ErrNotFound {
		return errors.NotFound("go.micro.client", err.Error())
	} else if err != nil {
		return errors.InternalServerError("go.micro.client", err.Error())
	}

	// check if we already have a deadline
	d, ok := ctx.Deadline()
	if !ok {
		// no deadline so we create a new one
		ctx, _ = context.WithTimeout(ctx, callOpts.RequestTimeout)
	} else {
		// got a deadline so no need to setup context
		// but we need to set the timeout we pass along
		opt := WithRequestTimeout(d.Sub(time.Now()))
		opt(&callOpts)
	}

	// should we noop right here?
	select {
	case <-ctx.Done():
		return errors.New("go.micro.client", fmt.Sprintf("%v", ctx.Err()), 408)
	default:
	}

	// make copy of call method
	rcall := r.call

	// wrap the call in reverse
	for i := len(callOpts.CallWrappers); i > 0; i-- {
		rcall = callOpts.CallWrappers[i-1](rcall)
	}

	// return errors.New("go.micro.client", "request timeout", 408)
	call := func(i int) error {
		// call backoff first. Someone may want an initial start delay
		t, err := callOpts.Backoff(ctx, request, i)
		if err != nil {
			return errors.InternalServerError("go.micro.client", err.Error())
		}

		// only sleep if greater than 0
		if t.Seconds() > 0 {
			time.Sleep(t)
		}

		// select next node
		node, err := next()
		if err != nil && err == selector.ErrNotFound {
			return errors.NotFound("go.micro.client", err.Error())
		} else if err != nil {
			return errors.InternalServerError("go.micro.client", err.Error())
		}

		// set the address
		address := node.Address
		if node.Port > 0 {
			address = fmt.Sprintf("%s:%d", address, node.Port)
		}

		// make the call
		err = rcall(ctx, address, request, response, callOpts)
		r.opts.Selector.Mark(request.Service(), node, err)
		return err
	}

	ch := make(chan error, callOpts.Retries)
	var gerr error

	for i := 0; i < callOpts.Retries; i++ {
		go func() {
			ch <- call(i)
		}()

		select {
		case <-ctx.Done():
			return errors.New("go.micro.client", fmt.Sprintf("%v", ctx.Err()), 408)
		case err := <-ch:
			// if the call succeeded lets bail early
			if err == nil {
				return nil
			}

			retry, rerr := callOpts.Retry(ctx, request, i, err)
			if rerr != nil {
				return rerr
			}

			if !retry {
				return err
			}

			gerr = err
		}
	}

	return gerr
}

func (r *rpcClient) StreamRemote(ctx context.Context, address string, request Request, opts ...CallOption) (Streamer, error) {
	// make a copy of call opts
	callOpts := r.opts.CallOptions
	for _, opt := range opts {
		opt(&callOpts)
	}
	return r.stream(ctx, address, request, callOpts)
}

func (r *rpcClient) Stream(ctx context.Context, request Request, opts ...CallOption) (Streamer, error) {
	// make a copy of call opts
	callOpts := r.opts.CallOptions
	for _, opt := range opts {
		opt(&callOpts)
	}

	// get next nodes from the selector
	next, err := r.opts.Selector.Select(request.Service(), callOpts.SelectOptions...)
	if err != nil && err == selector.ErrNotFound {
		return nil, errors.NotFound("go.micro.client", err.Error())
	} else if err != nil {
		return nil, errors.InternalServerError("go.micro.client", err.Error())
	}

	// check if we already have a deadline
	d, ok := ctx.Deadline()
	if !ok {
		// no deadline so we create a new one
		ctx, _ = context.WithTimeout(ctx, callOpts.RequestTimeout)
	} else {
		// got a deadline so no need to setup context
		// but we need to set the timeout we pass along
		opt := WithRequestTimeout(d.Sub(time.Now()))
		opt(&callOpts)
	}

	// should we noop right here?
	select {
	case <-ctx.Done():
		return nil, errors.New("go.micro.client", fmt.Sprintf("%v", ctx.Err()), 408)
	default:
	}

	call := func(i int) (Streamer, error) {
		// call backoff first. Someone may want an initial start delay
		t, err := callOpts.Backoff(ctx, request, i)
		if err != nil {
			return nil, errors.InternalServerError("go.micro.client", err.Error())
		}

		// only sleep if greater than 0
		if t.Seconds() > 0 {
			time.Sleep(t)
		}

		node, err := next()
		if err != nil && err == selector.ErrNotFound {
			return nil, errors.NotFound("go.micro.client", err.Error())
		} else if err != nil {
			return nil, errors.InternalServerError("go.micro.client", err.Error())
		}

		address := node.Address
		if node.Port > 0 {
			address = fmt.Sprintf("%s:%d", address, node.Port)
		}

		stream, err := r.stream(ctx, address, request, callOpts)
		r.opts.Selector.Mark(request.Service(), node, err)
		return stream, err
	}

	type response struct {
		stream Streamer
		err    error
	}

	ch := make(chan response, callOpts.Retries)
	var grr error

	for i := 0; i < callOpts.Retries; i++ {
		go func() {
			s, err := call(i)
			ch <- response{s, err}
		}()

		select {
		case <-ctx.Done():
			return nil, errors.New("go.micro.client", fmt.Sprintf("%v", ctx.Err()), 408)
		case rsp := <-ch:
			// if the call succeeded lets bail early
			if rsp.err == nil {
				return rsp.stream, nil
			}

			retry, rerr := callOpts.Retry(ctx, request, i, rsp.err)
			if rerr != nil {
				return nil, rerr
			}

			if !retry {
				return nil, rsp.err
			}

			grr = rsp.err
		}
	}

	return nil, grr
}

func (r *rpcClient) Publish(ctx context.Context, p Publication, opts ...PublishOption) error {
	md, ok := metadata.FromContext(ctx)
	if !ok {
		md = make(map[string]string)
	}
	md["Content-Type"] = p.ContentType()

	// encode message body
	cf, err := r.newCodec(p.ContentType())
	if err != nil {
		return errors.InternalServerError("go.micro.client", err.Error())
	}
	b := &buffer{bytes.NewBuffer(nil)}
	if err := cf(b).Write(&codec.Message{Type: codec.Publication}, p.Message()); err != nil {
		return errors.InternalServerError("go.micro.client", err.Error())
	}
	r.once.Do(func() {
		r.opts.Broker.Connect()
	})

	return r.opts.Broker.Publish(p.Topic(), &broker.Message{
		Header: md,
		Body:   b.Bytes(),
	})
}

func (r *rpcClient) NewPublication(topic string, message interface{}) Publication {
	return newRpcPublication(topic, message, r.opts.ContentType)
}

func (r *rpcClient) NewProtoPublication(topic string, message interface{}) Publication {
	return newRpcPublication(topic, message, "application/octet-stream")
}
func (r *rpcClient) NewRequest(service, method string, request interface{}, reqOpts ...RequestOption) Request {
	return newRpcRequest(service, method, request, r.opts.ContentType, reqOpts...)
}

func (r *rpcClient) NewProtoRequest(service, method string, request interface{}, reqOpts ...RequestOption) Request {
	return newRpcRequest(service, method, request, "application/octet-stream", reqOpts...)
}

func (r *rpcClient) NewJsonRequest(service, method string, request interface{}, reqOpts ...RequestOption) Request {
	return newRpcRequest(service, method, request, "application/json", reqOpts...)
}

func (r *rpcClient) String() string {
	return "rpc"
}
<commit_msg>Differentiate between request and call timeouts<commit_after>package client

import (
	"bytes"
	"fmt"
	"sync"
	"time"

	"github.com/micro/go-micro/broker"
	"github.com/micro/go-micro/codec"
	"github.com/micro/go-micro/errors"
	"github.com/micro/go-micro/metadata"
	"github.com/micro/go-micro/selector"
	"github.com/micro/go-micro/transport"

	"golang.org/x/net/context"
)

type rpcClient struct {
	once sync.Once
	opts Options
	pool *pool
}

func newRpcClient(opt ...Option) Client {
	opts := newOptions(opt...)

	rc := &rpcClient{
		once: sync.Once{},
		opts: opts,
		pool: newPool(opts.PoolSize, opts.PoolTTL),
	}

	c := Client(rc)

	// wrap in reverse
	for i := len(opts.Wrappers); i > 0; i-- {
		c = opts.Wrappers[i-1](c)
	}

	return c
}

func (r *rpcClient) newCodec(contentType string) (codec.NewCodec, error) {
	if c, ok := r.opts.Codecs[contentType]; ok {
		return c, nil
	}
	if cf, ok := defaultCodecs[contentType]; ok {
		return cf, nil
	}
	return nil, fmt.Errorf("Unsupported Content-Type: %s", contentType)
}

func (r *rpcClient) call(ctx context.Context, address string, req Request, resp interface{}, opts CallOptions) error {
	msg := &transport.Message{
		Header: make(map[string]string),
	}

	md, ok := metadata.FromContext(ctx)
	if ok {
		for k, v := range md {
			msg.Header[k] = v
		}
	}

	// set timeout in nanoseconds
	msg.Header["Timeout"] = fmt.Sprintf("%d", opts.RequestTimeout)
	// set the content type for the request
	msg.Header["Content-Type"] = req.ContentType()
	// set the accept header
	msg.Header["Accept"] = req.ContentType()

	cf, err := r.newCodec(req.ContentType())
	if err != nil {
		return errors.InternalServerError("go.micro.client", err.Error())
	}

	var grr error
	c, err := r.pool.getConn(address, r.opts.Transport, transport.WithTimeout(opts.DialTimeout))
	if err != nil {
		return errors.InternalServerError("go.micro.client", fmt.Sprintf("Error sending request: %v", err))
	}
	defer func() {
		// defer execution of release
		r.pool.release(address, c, grr)
	}()

	stream := &rpcStream{
		context: ctx,
		request: req,
		closed:  make(chan bool),
		codec:   newRpcPlusCodec(msg, c, cf),
	}
	defer stream.Close()

	ch := make(chan error, 1)

	go func() {
		defer func() {
			if r := recover(); r != nil {
				ch <- errors.InternalServerError("go.micro.client", "request error")
			}
		}()

		// send request
		if err := stream.Send(req.Request()); err != nil {
			ch <- err
			return
		}

		// recv request
		if err := stream.Recv(resp); err != nil {
			ch <- err
			return
		}

		// success
		ch <- nil
	}()

	select {
	case err := <-ch:
		grr = err
		return err
	case <-ctx.Done():
		grr = ctx.Err()
		return errors.New("go.micro.client", fmt.Sprintf("request timeout: %v", ctx.Err()), 408)
	}
}

func (r *rpcClient) stream(ctx context.Context, address string, req Request, opts CallOptions) (Streamer, error) {
	msg := &transport.Message{
		Header: make(map[string]string),
	}

	md, ok := metadata.FromContext(ctx)
	if ok {
		for k, v := range md {
			msg.Header[k] = v
		}
	}

	// set timeout in nanoseconds
	msg.Header["Timeout"] = fmt.Sprintf("%d", opts.RequestTimeout)
	// set the content type for the request
	msg.Header["Content-Type"] = req.ContentType()
	// set the accept header
	msg.Header["Accept"] = req.ContentType()

	cf, err := r.newCodec(req.ContentType())
	if err != nil {
		return nil, errors.InternalServerError("go.micro.client", err.Error())
	}

	c, err := r.opts.Transport.Dial(address, transport.WithStream(), transport.WithTimeout(opts.DialTimeout))
	if err != nil {
		return nil, errors.InternalServerError("go.micro.client", fmt.Sprintf("Error sending request: %v", err))
	}

	stream := &rpcStream{
		context: ctx,
		request: req,
		closed:  make(chan bool),
		codec:   newRpcPlusCodec(msg, c, cf),
	}

	ch := make(chan error, 1)

	go func() {
		ch <- stream.Send(req.Request())
	}()

	var grr error

	select {
	case err := <-ch:
		grr = err
	case <-ctx.Done():
		grr = errors.New("go.micro.client", fmt.Sprintf("request timeout: %v", ctx.Err()), 408)
	}

	if grr != nil {
		stream.Close()
		return nil, grr
	}

	return stream, nil
}

func (r *rpcClient) Init(opts ...Option) error {
	size := r.opts.PoolSize
	ttl := r.opts.PoolTTL

	for _, o := range opts {
		o(&r.opts)
	}

	// recreate the pool if the options changed
	if size != r.opts.PoolSize || ttl != r.opts.PoolTTL {
		r.pool = newPool(r.opts.PoolSize, r.opts.PoolTTL)
	}

	return nil
}

func (r *rpcClient) Options() Options {
	return r.opts
}

func (r *rpcClient) CallRemote(ctx context.Context, address string, request Request, response interface{}, opts ...CallOption) error {
	// make a copy of call opts
	callOpts := r.opts.CallOptions
	for _, opt := range opts {
		opt(&callOpts)
	}
	return r.call(ctx, address, request, response, callOpts)
}

func (r *rpcClient) Call(ctx context.Context, request Request, response interface{}, opts ...CallOption) error {
	// make a copy of call opts
	callOpts := r.opts.CallOptions
	for _, opt := range opts {
		opt(&callOpts)
	}

	// get next nodes from the selector
	next, err := r.opts.Selector.Select(request.Service(), callOpts.SelectOptions...)
	if err != nil && err == selector.ErrNotFound {
		return errors.NotFound("go.micro.client", err.Error())
	} else if err != nil {
		return errors.InternalServerError("go.micro.client", err.Error())
	}

	// check if we already have a deadline
	d, ok := ctx.Deadline()
	if !ok {
		// no deadline so we create a new one
		ctx, _ = context.WithTimeout(ctx, callOpts.RequestTimeout)
	} else {
		// got a deadline so no need to setup context
		// but we need to set the timeout we pass along
		opt := WithRequestTimeout(d.Sub(time.Now()))
		opt(&callOpts)
	}

	// should we noop right here?
	select {
	case <-ctx.Done():
		return errors.New("go.micro.client", fmt.Sprintf("%v", ctx.Err()), 408)
	default:
	}

	// make copy of call method
	rcall := r.call

	// wrap the call in reverse
	for i := len(callOpts.CallWrappers); i > 0; i-- {
		rcall = callOpts.CallWrappers[i-1](rcall)
	}

	// return errors.New("go.micro.client", "request timeout", 408)
	call := func(i int) error {
		// call backoff first. Someone may want an initial start delay
		t, err := callOpts.Backoff(ctx, request, i)
		if err != nil {
			return errors.InternalServerError("go.micro.client", err.Error())
		}

		// only sleep if greater than 0
		if t.Seconds() > 0 {
			time.Sleep(t)
		}

		// select next node
		node, err := next()
		if err != nil && err == selector.ErrNotFound {
			return errors.NotFound("go.micro.client", err.Error())
		} else if err != nil {
			return errors.InternalServerError("go.micro.client", err.Error())
		}

		// set the address
		address := node.Address
		if node.Port > 0 {
			address = fmt.Sprintf("%s:%d", address, node.Port)
		}

		// make the call
		err = rcall(ctx, address, request, response, callOpts)
		r.opts.Selector.Mark(request.Service(), node, err)
		return err
	}

	ch := make(chan error, callOpts.Retries)
	var gerr error

	for i := 0; i < callOpts.Retries; i++ {
		go func() {
			ch <- call(i)
		}()

		select {
		case <-ctx.Done():
			return errors.New("go.micro.client", fmt.Sprintf("call timeout: %v", ctx.Err()), 408)
		case err := <-ch:
			// if the call succeeded lets bail early
			if err == nil {
				return nil
			}

			retry, rerr := callOpts.Retry(ctx, request, i, err)
			if rerr != nil {
				return rerr
			}

			if !retry {
				return err
			}

			gerr = err
		}
	}

	return gerr
}

func (r *rpcClient) StreamRemote(ctx context.Context, address string, request Request, opts ...CallOption) (Streamer, error) {
	// make a copy of call opts
	callOpts := r.opts.CallOptions
	for _, opt := range opts {
		opt(&callOpts)
	}
	return r.stream(ctx, address, request, callOpts)
}

func (r *rpcClient) Stream(ctx context.Context, request Request, opts ...CallOption) (Streamer, error) {
	// make a copy of call opts
	callOpts := r.opts.CallOptions
	for _, opt := range opts {
		opt(&callOpts)
	}

	// get next nodes from the selector
	next, err := r.opts.Selector.Select(request.Service(), callOpts.SelectOptions...)
	if err != nil && err == selector.ErrNotFound {
		return nil, errors.NotFound("go.micro.client", err.Error())
	} else if err != nil {
		return nil, errors.InternalServerError("go.micro.client", err.Error())
	}

	// check if we already have a deadline
	d, ok := ctx.Deadline()
	if !ok {
		// no deadline so we create a new one
		ctx, _ = context.WithTimeout(ctx, callOpts.RequestTimeout)
	} else {
		// got a deadline so no need to setup context
		// but we need to set the timeout we pass along
		opt := WithRequestTimeout(d.Sub(time.Now()))
		opt(&callOpts)
	}

	// should we noop right here?
	select {
	case <-ctx.Done():
		return nil, errors.New("go.micro.client", fmt.Sprintf("%v", ctx.Err()), 408)
	default:
	}

	call := func(i int) (Streamer, error) {
		// call backoff first. Someone may want an initial start delay
		t, err := callOpts.Backoff(ctx, request, i)
		if err != nil {
			return nil, errors.InternalServerError("go.micro.client", err.Error())
		}

		// only sleep if greater than 0
		if t.Seconds() > 0 {
			time.Sleep(t)
		}

		node, err := next()
		if err != nil && err == selector.ErrNotFound {
			return nil, errors.NotFound("go.micro.client", err.Error())
		} else if err != nil {
			return nil, errors.InternalServerError("go.micro.client", err.Error())
		}

		address := node.Address
		if node.Port > 0 {
			address = fmt.Sprintf("%s:%d", address, node.Port)
		}

		stream, err := r.stream(ctx, address, request, callOpts)
		r.opts.Selector.Mark(request.Service(), node, err)
		return stream, err
	}

	type response struct {
		stream Streamer
		err    error
	}

	ch := make(chan response, callOpts.Retries)
	var grr error

	for i := 0; i < callOpts.Retries; i++ {
		go func() {
			s, err := call(i)
			ch <- response{s, err}
		}()

		select {
		case <-ctx.Done():
			return nil, errors.New("go.micro.client", fmt.Sprintf("call timeout: %v", ctx.Err()), 408)
		case rsp := <-ch:
			// if the call succeeded lets bail early
			if rsp.err == nil {
				return rsp.stream, nil
			}

			retry, rerr := callOpts.Retry(ctx, request, i, rsp.err)
			if rerr != nil {
				return nil, rerr
			}

			if !retry {
				return nil, rsp.err
			}

			grr = rsp.err
		}
	}

	return nil, grr
}

func (r *rpcClient) Publish(ctx context.Context, p Publication, opts ...PublishOption) error {
	md, ok := metadata.FromContext(ctx)
	if !ok {
		md = make(map[string]string)
	}
	md["Content-Type"] = p.ContentType()

	// encode message body
	cf, err := r.newCodec(p.ContentType())
	if err != nil {
		return errors.InternalServerError("go.micro.client", err.Error())
	}
	b := &buffer{bytes.NewBuffer(nil)}
	if err := cf(b).Write(&codec.Message{Type: codec.Publication}, p.Message()); err != nil {
		return errors.InternalServerError("go.micro.client", err.Error())
	}
	r.once.Do(func() {
		r.opts.Broker.Connect()
	})

	return r.opts.Broker.Publish(p.Topic(), &broker.Message{
		Header: md,
		Body:   b.Bytes(),
	})
}

func (r *rpcClient) NewPublication(topic string, message interface{}) Publication {
	return newRpcPublication(topic, message, r.opts.ContentType)
}

func (r *rpcClient) NewProtoPublication(topic string, message interface{}) Publication {
	return newRpcPublication(topic, message, "application/octet-stream")
}
func (r *rpcClient) NewRequest(service, method string, request interface{}, reqOpts ...RequestOption) Request {
	return newRpcRequest(service, method, request, r.opts.ContentType, reqOpts...)
}

func (r *rpcClient) NewProtoRequest(service, method string, request interface{}, reqOpts ...RequestOption) Request {
	return newRpcRequest(service, method, request, "application/octet-stream", reqOpts...)
}

func (r *rpcClient) NewJsonRequest(service, method string, request interface{}, reqOpts ...RequestOption) Request {
	return newRpcRequest(service, method, request, "application/json", reqOpts...)
}

func (r *rpcClient) String() string {
	return "rpc"
}
<|endoftext|>


<commit_before>package appui

import (
	"context"

	"github.com/docker/docker/api/types/events"
	"github.com/moncho/dry/docker"
	"github.com/moncho/dry/ui/termui"
)

//RegisterWidget registers the given widget for updates from the given source
func RegisterWidget(source docker.SourceType, w termui.Widget) {
	docker.GlobalRegistry.Register(
		source,
		func(ctx context.Context, message events.Message) error {
			return w.Unmount()
		})
}
<commit_msg>Refresh only if a certain time has passed since the previous event<commit_after>package appui

import (
	"context"
	"time"

	"github.com/docker/docker/api/types/events"
	"github.com/moncho/dry/docker"
	"github.com/moncho/dry/ui/termui"
)

var timeBetweenRefresh = 500 * time.Millisecond

//RegisterWidget registers the given widget for updates from the given source
func RegisterWidget(source docker.SourceType, w termui.Widget) {
	last := time.Now()
	docker.GlobalRegistry.Register(
		source,
		func(ctx context.Context, message events.Message) error {
			if time.Now().Sub(last) > timeBetweenRefresh {
				last = time.Now()

				return w.Unmount()
			}
			return nil
		})
}
<|endoftext|>