<commit_before>// Command deplist prints a detailed list of package imports for a package.
// The output contains the path to the source on the GOPATH for those imported packages.
package main

import (
	"flag"
	"fmt"
	"go/build"
	"io"
	"log"
	"os"
	"path/filepath"
	"strings"
	"text/tabwriter"
)

var (
	tags   = flag.String("tags", "", "comma-separated list of build tags to apply")
	goroot = flag.Bool("goroot", false, "include imports in GOROOT")
	tsv    = flag.Bool("tsv", false, "use only a single tab between columns")
)

type flusher interface {
	Flush() error
}

func main() {
	flag.Usage = func() {
		fmt.Fprintln(os.Stderr, "usage: pkglist [-tags] [-goroot] <dirs...>")
		flag.PrintDefaults()
		os.Exit(1)
	}

	flag.Parse()

	if flag.NArg() == 0 {
		flag.Usage()
	}

	buildctx := build.Default
	buildctx.BuildTags = strings.Split(*tags, ",")

	visited := make(map[imp]bool)
	var imports []imp

	for _, dir := range flag.Args() {
		abs, err := filepath.Abs(dir)
		if err != nil {
			log.Fatalf("could not get absolute path for dir %q: %v", dir, err)
		}

		pkg, err := buildctx.ImportDir(abs, 0)
		if err != nil {
			log.Fatalf("could not get package for dir %q: %v", dir, err)
		}

		for _, importPath := range pkg.Imports {
			i := imp{path: importPath, from: abs}
			imports = append(imports, i)
		}
	}

	var w io.Writer = os.Stdout
	if !*tsv {
		w = tabwriter.NewWriter(os.Stdout, 80, 4, 0, ' ', 0)
	}

	for len(imports) != 0 {
		i := imports[0]
		imports = imports[1:] // shift

		if _, ok := visited[i]; ok || i.path == "C" {
			continue
		}
		visited[i] = true

		pkg, err := buildctx.Import(i.path, i.from, 0)
		if err != nil {
			log.Fatalf("could not get package %q, imported from %q: %v", i.path, i.from, err)
		}

		if !*goroot && pkg.Goroot {
			continue
		}

		fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", i.from, i.path, pkg.SrcRoot, pkg.ImportPath)

		for _, i := range pkg.Imports {
			imports = append(imports, imp{path: i, from: pkg.Dir})
		}
	}
	if f, ok := w.(flusher); ok {
		f.Flush()
	}
}

type imp struct {
	path, from string
}
<commit_msg>deplist: tweak tabwriter output<commit_after>// Command deplist prints a detailed list of package imports for a package.
// The output contains the path to the source on the GOPATH for those imported packages.
package main

import (
	"flag"
	"fmt"
	"go/build"
	"io"
	"log"
	"os"
	"path/filepath"
	"strings"
	"text/tabwriter"
)

var (
	tags   = flag.String("tags", "", "comma-separated list of build tags to apply")
	goroot = flag.Bool("goroot", false, "include imports in GOROOT")
	tsv    = flag.Bool("tsv", false, "use only a single tab between columns")
)

type flusher interface {
	Flush() error
}

func main() {
	flag.Usage = func() {
		fmt.Fprintln(os.Stderr, "usage: pkglist [-tags] [-goroot] <dirs...>")
		flag.PrintDefaults()
		os.Exit(1)
	}

	flag.Parse()

	if flag.NArg() == 0 {
		flag.Usage()
	}

	buildctx := build.Default
	buildctx.BuildTags = strings.Split(*tags, ",")

	visited := make(map[imp]bool)
	var imports []imp

	for _, dir := range flag.Args() {
		abs, err := filepath.Abs(dir)
		if err != nil {
			log.Fatalf("could not get absolute path for dir %q: %v", dir, err)
		}

		pkg, err := buildctx.ImportDir(abs, 0)
		if err != nil {
			log.Fatalf("could not get package for dir %q: %v", dir, err)
		}

		for _, importPath := range pkg.Imports {
			i := imp{path: importPath, from: abs}
			imports = append(imports, i)
		}
	}

	var w io.Writer = os.Stdout
	if !*tsv {
		w = tabwriter.NewWriter(os.Stdout, 8, 0, 4, ' ', 0)
	}

	for len(imports) != 0 {
		i := imports[0]
		imports = imports[1:] // shift

		if _, ok := visited[i]; ok || i.path == "C" {
			continue
		}
		visited[i] = true

		pkg, err := buildctx.Import(i.path, i.from, 0)
		if err != nil {
			log.Fatalf("could not get package %q, imported from %q: %v", i.path, i.from, err)
		}

		if !*goroot && pkg.Goroot {
			continue
		}

		fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", i.from, i.path, pkg.SrcRoot, pkg.ImportPath)

		for _, i := range pkg.Imports {
			imports = append(imports, imp{path: i, from: pkg.Dir})
		}
	}
	if f, ok := w.(flusher); ok {
		f.Flush()
	}
}

type imp struct {
	path, from string
}
<|endoftext|>


<commit_before>package system

import (
	"fmt"
	"runtime"
	"time"

	"golang.org/x/net/context"

	"github.com/docker/docker/api/types"
	"github.com/docker/docker/cli"
	"github.com/docker/docker/cli/command"
	"github.com/docker/docker/dockerversion"
	"github.com/docker/docker/utils/templates"
	"github.com/spf13/cobra"
)

var versionTemplate = `Client:
 Version:      {{.Client.Version}}
 API version:  {{.Client.APIVersion}}
 Go version:   {{.Client.GoVersion}}
 Git commit:   {{.Client.GitCommit}}
 Built:        {{.Client.BuildTime}}
 OS/Arch:      {{.Client.Os}}/{{.Client.Arch}}{{if .ServerOK}}

Server:
 Version:             {{.Server.Version}}
 API version:         {{.Server.APIVersion}}
 Minimum API version: {{.Server.MinAPIVersion}}
 Go version:          {{.Server.GoVersion}}
 Git commit:          {{.Server.GitCommit}}
 Built:               {{.Server.BuildTime}}
 OS/Arch:             {{.Server.Os}}/{{.Server.Arch}}
 Experimental:        {{.Server.Experimental}}{{end}}`

type versionOptions struct {
	format string
}

// NewVersionCommand creates a new cobra.Command for `docker version`
func NewVersionCommand(dockerCli *command.DockerCli) *cobra.Command {
	var opts versionOptions

	cmd := &cobra.Command{
		Use:   "version [OPTIONS]",
		Short: "Show the Docker version information",
		Args:  cli.NoArgs,
		RunE: func(cmd *cobra.Command, args []string) error {
			return runVersion(dockerCli, &opts)
		},
	}

	flags := cmd.Flags()

	flags.StringVarP(&opts.format, "format", "f", "", "Format the output using the given Go template")

	return cmd
}

func runVersion(dockerCli *command.DockerCli, opts *versionOptions) error {
	ctx := context.Background()

	templateFormat := versionTemplate
	if opts.format != "" {
		templateFormat = opts.format
	}

	tmpl, err := templates.Parse(templateFormat)
	if err != nil {
		return cli.StatusError{StatusCode: 64,
			Status: "Template parsing error: " + err.Error()}
	}

	APIVersion := dockerCli.Client().ClientVersion()
	if defaultAPIVersion := dockerCli.DefaultVersion(); APIVersion != defaultAPIVersion {
		APIVersion = fmt.Sprintf("%s (downgraded from %s)", APIVersion, defaultAPIVersion)
	}

	vd := types.VersionResponse{
		Client: &types.Version{
			Version:    dockerversion.Version,
			APIVersion: APIVersion,
			GoVersion:  runtime.Version(),
			GitCommit:  dockerversion.GitCommit,
			BuildTime:  dockerversion.BuildTime,
			Os:         runtime.GOOS,
			Arch:       runtime.GOARCH,
		},
	}

	serverVersion, err := dockerCli.Client().ServerVersion(ctx)
	if err == nil {
		vd.Server = &serverVersion
	}

	// first we need to make BuildTime more human friendly
	t, errTime := time.Parse(time.RFC3339Nano, vd.Client.BuildTime)
	if errTime == nil {
		vd.Client.BuildTime = t.Format(time.ANSIC)
	}

	if vd.ServerOK() {
		t, errTime = time.Parse(time.RFC3339Nano, vd.Server.BuildTime)
		if errTime == nil {
			vd.Server.BuildTime = t.Format(time.ANSIC)
		}
	}

	if err2 := tmpl.Execute(dockerCli.Out(), vd); err2 != nil && err == nil {
		err = err2
	}
	dockerCli.Out().Write([]byte{'\n'})
	return err
}
<commit_msg>Align output of docker version again<commit_after>package system

import (
	"fmt"
	"runtime"
	"time"

	"golang.org/x/net/context"

	"github.com/docker/docker/api/types"
	"github.com/docker/docker/cli"
	"github.com/docker/docker/cli/command"
	"github.com/docker/docker/dockerversion"
	"github.com/docker/docker/utils/templates"
	"github.com/spf13/cobra"
)

var versionTemplate = `Client:
 Version:      {{.Client.Version}}
 API version:  {{.Client.APIVersion}}
 Go version:   {{.Client.GoVersion}}
 Git commit:   {{.Client.GitCommit}}
 Built:        {{.Client.BuildTime}}
 OS/Arch:      {{.Client.Os}}/{{.Client.Arch}}{{if .ServerOK}}

Server:
 Version:      {{.Server.Version}}
 API version:  {{.Server.APIVersion}} (minimum version {{.Server.MinAPIVersion}})
 Go version:   {{.Server.GoVersion}}
 Git commit:   {{.Server.GitCommit}}
 Built:        {{.Server.BuildTime}}
 OS/Arch:      {{.Server.Os}}/{{.Server.Arch}}
 Experimental: {{.Server.Experimental}}{{end}}`

type versionOptions struct {
	format string
}

// NewVersionCommand creates a new cobra.Command for `docker version`
func NewVersionCommand(dockerCli *command.DockerCli) *cobra.Command {
	var opts versionOptions

	cmd := &cobra.Command{
		Use:   "version [OPTIONS]",
		Short: "Show the Docker version information",
		Args:  cli.NoArgs,
		RunE: func(cmd *cobra.Command, args []string) error {
			return runVersion(dockerCli, &opts)
		},
	}

	flags := cmd.Flags()

	flags.StringVarP(&opts.format, "format", "f", "", "Format the output using the given Go template")

	return cmd
}

func runVersion(dockerCli *command.DockerCli, opts *versionOptions) error {
	ctx := context.Background()

	templateFormat := versionTemplate
	if opts.format != "" {
		templateFormat = opts.format
	}

	tmpl, err := templates.Parse(templateFormat)
	if err != nil {
		return cli.StatusError{StatusCode: 64,
			Status: "Template parsing error: " + err.Error()}
	}

	APIVersion := dockerCli.Client().ClientVersion()
	if defaultAPIVersion := dockerCli.DefaultVersion(); APIVersion != defaultAPIVersion {
		APIVersion = fmt.Sprintf("%s (downgraded from %s)", APIVersion, defaultAPIVersion)
	}

	vd := types.VersionResponse{
		Client: &types.Version{
			Version:    dockerversion.Version,
			APIVersion: APIVersion,
			GoVersion:  runtime.Version(),
			GitCommit:  dockerversion.GitCommit,
			BuildTime:  dockerversion.BuildTime,
			Os:         runtime.GOOS,
			Arch:       runtime.GOARCH,
		},
	}

	serverVersion, err := dockerCli.Client().ServerVersion(ctx)
	if err == nil {
		vd.Server = &serverVersion
	}

	// first we need to make BuildTime more human friendly
	t, errTime := time.Parse(time.RFC3339Nano, vd.Client.BuildTime)
	if errTime == nil {
		vd.Client.BuildTime = t.Format(time.ANSIC)
	}

	if vd.ServerOK() {
		t, errTime = time.Parse(time.RFC3339Nano, vd.Server.BuildTime)
		if errTime == nil {
			vd.Server.BuildTime = t.Format(time.ANSIC)
		}
	}

	if err2 := tmpl.Execute(dockerCli.Out(), vd); err2 != nil && err == nil {
		err = err2
	}
	dockerCli.Out().Write([]byte{'\n'})
	return err
}
<|endoftext|>


<commit_before>package commands

import (
	"io"
	"os"

	"github.com/git-lfs/git-lfs/errors"
	"github.com/git-lfs/git-lfs/lfs"
	"github.com/git-lfs/git-lfs/progress"
	"github.com/spf13/cobra"
)

// clean cleans an object read from the given `io.Reader`, "from", and writes
// out a corresponding pointer to the `io.Writer`, "to". If there were any
// errors encountered along the way, they will be returned immediately if the
// error is non-fatal, otherwise they will halt using the built in
// `commands.Panic`.
//
// If the object read from "from" is _already_ a clean pointer, then it will be
// written out verbatim to "to", without trying to make it a pointer again.
func clean(to io.Writer, from io.Reader, fileName string) error {
	var cb progress.CopyCallback
	var file *os.File
	var fileSize int64

	if len(fileName) > 0 {
		stat, err := os.Stat(fileName)
		if err == nil && stat != nil {
			fileSize = stat.Size()

			localCb, localFile, err := lfs.CopyCallbackFile("clean", fileName, 1, 1)
			if err != nil {
				Error(err.Error())
			} else {
				cb = localCb
				file = localFile
			}
		}
	}

	cleaned, err := lfs.PointerClean(from, fileName, fileSize, cb)
	if file != nil {
		file.Close()
	}

	if cleaned != nil {
		defer cleaned.Teardown()
	}

	if errors.IsCleanPointerError(err) {
		// If the contents read from the working directory was _already_
		// a pointer, we'll get a `CleanPointerError`, with the context
		// containing the bytes that we should write back out to Git.

		_, err = to.Write(errors.GetContext(err, "bytes").([]byte))
		return err
	}

	if err != nil {
		Panic(err, "Error cleaning LFS object: %s", err)
	}

	tmpfile := cleaned.Filename
	mediafile, err := lfs.LocalMediaPath(cleaned.Oid)
	if err != nil {
		Panic(err, "Unable to get local media path.")
	}

	if stat, _ := os.Stat(mediafile); stat != nil {
		if stat.Size() != cleaned.Size && len(cleaned.Pointer.Extensions) == 0 {
			Exit("Files don't match:\n%s\n%s", mediafile, tmpfile)
		}
		Debug("%s exists", mediafile)
	} else {
		if err := os.Rename(tmpfile, mediafile); err != nil {
			Panic(err, "Unable to move %s to %s\n", tmpfile, mediafile)
		}

		Debug("Writing %s", mediafile)
	}

	_, err = lfs.EncodePointer(to, cleaned.Pointer)
	return err
}

func cleanCommand(cmd *cobra.Command, args []string) {
	requireStdin("This command should be run by the Git 'clean' filter")
	lfs.InstallHooks(false)

	var fileName string
	if len(args) > 0 {
		fileName = args[0]
	}

	if err := clean(os.Stdout, os.Stdin, fileName); err != nil {
		Error(err.Error())
	}
}

func init() {
	RegisterCommand("clean", cleanCommand, nil)
}
<commit_msg>commands/clean: use ExitWithError() helper<commit_after>package commands

import (
	"io"
	"os"

	"github.com/git-lfs/git-lfs/errors"
	"github.com/git-lfs/git-lfs/lfs"
	"github.com/git-lfs/git-lfs/progress"
	"github.com/spf13/cobra"
)

// clean cleans an object read from the given `io.Reader`, "from", and writes
// out a corresponding pointer to the `io.Writer`, "to". If there were any
// errors encountered along the way, they will be returned immediately if the
// error is non-fatal, otherwise they will halt using the built in
// `commands.Panic`.
//
// If the object read from "from" is _already_ a clean pointer, then it will be
// written out verbatim to "to", without trying to make it a pointer again.
func clean(to io.Writer, from io.Reader, fileName string) error {
	var cb progress.CopyCallback
	var file *os.File
	var fileSize int64

	if len(fileName) > 0 {
		stat, err := os.Stat(fileName)
		if err == nil && stat != nil {
			fileSize = stat.Size()

			localCb, localFile, err := lfs.CopyCallbackFile("clean", fileName, 1, 1)
			if err != nil {
				Error(err.Error())
			} else {
				cb = localCb
				file = localFile
			}
		}
	}

	cleaned, err := lfs.PointerClean(from, fileName, fileSize, cb)
	if file != nil {
		file.Close()
	}

	if cleaned != nil {
		defer cleaned.Teardown()
	}

	if errors.IsCleanPointerError(err) {
		// If the contents read from the working directory was _already_
		// a pointer, we'll get a `CleanPointerError`, with the context
		// containing the bytes that we should write back out to Git.

		_, err = to.Write(errors.GetContext(err, "bytes").([]byte))
		return err
	}

	if err != nil {
		ExitWithError(errors.Wrap(err, "Error cleaning LFS object"))
	}

	tmpfile := cleaned.Filename
	mediafile, err := lfs.LocalMediaPath(cleaned.Oid)
	if err != nil {
		Panic(err, "Unable to get local media path.")
	}

	if stat, _ := os.Stat(mediafile); stat != nil {
		if stat.Size() != cleaned.Size && len(cleaned.Pointer.Extensions) == 0 {
			Exit("Files don't match:\n%s\n%s", mediafile, tmpfile)
		}
		Debug("%s exists", mediafile)
	} else {
		if err := os.Rename(tmpfile, mediafile); err != nil {
			Panic(err, "Unable to move %s to %s\n", tmpfile, mediafile)
		}

		Debug("Writing %s", mediafile)
	}

	_, err = lfs.EncodePointer(to, cleaned.Pointer)
	return err
}

func cleanCommand(cmd *cobra.Command, args []string) {
	requireStdin("This command should be run by the Git 'clean' filter")
	lfs.InstallHooks(false)

	var fileName string
	if len(args) > 0 {
		fileName = args[0]
	}

	if err := clean(os.Stdout, os.Stdin, fileName); err != nil {
		Error(err.Error())
	}
}

func init() {
	RegisterCommand("clean", cleanCommand, nil)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"

	"github.com/micro/cli"
	micro "github.com/micro/go-micro"
	proto "github.com/micro/go-micro/examples/service/proto"
	"golang.org/x/net/context"
)

/*

Example usage of top level service initialisation

*/

type Greeter struct{}

func (g *Greeter) Hello(ctx context.Context, req *proto.HelloRequest, rsp *proto.HelloResponse) error {
	rsp.Greeting = "Hello " + req.Name
	return nil
}

// Setup and the client
func client(service micro.Service) {
	// Create new greeter client
	greeter := proto.NewGreeterClient("greeter", service.Client())

	// Call the greeter
	rsp, err := greeter.Hello(context.TODO(), &proto.HelloRequest{Name: "John"})
	if err != nil {
		fmt.Println(err)
	}

	// Print response
	fmt.Println(rsp.Greeting)
}

func main() {
	// Create a new service. Optionally include some options here.
	service := micro.NewService(
		micro.Name("greeter"),
		micro.Version("latest"),
		micro.Metadata(map[string]string{
			"type": "helloworld",
		}),

		// Setup some flags. Specify --client to run the client

		// Add runtime flags
		// We could do this below too
		micro.Flags(cli.BoolFlag{
			Name:  "client",
			Usage: "Launch the client",
		}),
	)

	// Init will parse the command line flags. Any flags set will
	// override the above settings. Options defined here will
	// override anything set on the command line.
	service.Init(
		// Add runtime action
		// We could actually do this above
		micro.Action(func(c *cli.Context) {
			if c.Bool("client") {
				client(service)
				os.Exit(0)
			}
		}),
	)

	// By default we'll run the server unless the flags catch us

	// Setup the server

	// Register handler
	proto.RegisterGreeterHandler(service.Server(), new(Greeter))

	// Run the server
	if err := service.Run(); err != nil {
		fmt.Println(err)
	}
}
<commit_msg>update client<commit_after>package main

import (
	"fmt"
	"os"

	"github.com/micro/cli"
	"github.com/micro/go-micro"
	proto "github.com/micro/go-micro/examples/service/proto"
	"golang.org/x/net/context"
)

/*

Example usage of top level service initialisation

*/

type Greeter struct{}

func (g *Greeter) Hello(ctx context.Context, req *proto.HelloRequest, rsp *proto.HelloResponse) error {
	rsp.Greeting = "Hello " + req.Name
	return nil
}

// Setup and the client
func runClient(service micro.Service) {
	// Create new greeter client
	greeter := proto.NewGreeterClient("greeter", service.Client())

	// Call the greeter
	rsp, err := greeter.Hello(context.TODO(), &proto.HelloRequest{Name: "John"})
	if err != nil {
		fmt.Println(err)
		return
	}

	// Print response
	fmt.Println(rsp.Greeting)
}

func main() {
	// Create a new service. Optionally include some options here.
	service := micro.NewService(
		micro.Name("greeter"),
		micro.Version("latest"),
		micro.Metadata(map[string]string{
			"type": "helloworld",
		}),

		// Setup some flags. Specify --client to run the client

		// Add runtime flags
		// We could do this below too
		micro.Flags(cli.BoolFlag{
			Name:  "client",
			Usage: "Launch the client",
		}),
	)

	// Init will parse the command line flags. Any flags set will
	// override the above settings. Options defined here will
	// override anything set on the command line.
	service.Init(
		// Add runtime action
		// We could actually do this above
		micro.Action(func(c *cli.Context) {
			if c.Bool("client") {
				runClient(service)
				os.Exit(0)
			}
		}),
	)

	// By default we'll run the server unless the flags catch us

	// Setup the server

	// Register handler
	proto.RegisterGreeterHandler(service.Server(), new(Greeter))

	// Run the server
	if err := service.Run(); err != nil {
		fmt.Println(err)
	}
}
<|endoftext|>


<commit_before>package controllers

import (
	"io/ioutil"
	"encoding/json"
	"net/http"
	"github.com/gorilla/mux"
	"github.com/beeker1121/resounden/api/v1/utils"
	"github.com/beeker1121/resounden/api/v1/models"
)

type PlaylistController struct {
	APIKey string
}

func NewPlaylistController(apiKey string) *PlaylistController {
	return &PlaylistController{apiKey}
}

func (p *PlaylistController) Register(router *mux.Router) {
	router.HandleFunc("/api/v1/playlist/", p.getPlaylist)
	router.HandleFunc("/api/v1/playlist/{username}", p.getPlaylist)
}

func (p *PlaylistController) getPlaylist(w http.ResponseWriter, r *http.Request) {
	// Get the request variables
	vars := mux.Vars(r)

	// If the next_href param was passed to us, use that for the URL
	nextHref := r.URL.Query().Get("next_href")

	if nextHref == "" {
		nextHref = "http://api.soundcloud.com/users/" + vars["username"] + "/favorites?client_id=" + p.APIKey + "&linked_partitioning=1"
	}

	// Call the Soundcloud API
	resp, err := http.Get(nextHref)
	defer resp.Body.Close()
	if err != nil {
		utils.WriteError(utils.Error{404, "User not found"}, w)
		return
	}

	// Store the body of the response, using ioutil since it's a stream
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		utils.WriteError(utils.Error{500, "Internal server error"}, w)
		return
	}

	// Check for an error response first
	var jsonError utils.Errors

	// If we can't decode this json into our Errors struct type
	if err := json.Unmarshal(body, &jsonError); err != nil || len(jsonError.Errors) <= 0 {
		// Try decoding it into our Collection type
		var collection models.Collection

		// If we can't decode this json into our struct type
		if err := json.Unmarshal(body, &collection); err != nil {
			utils.WriteError(utils.Error{500, "Can't decode JSON into struct type"}, w)
			return
		}

		// Append the Soundcloud Client ID to stream_url for each track
		for i, _ := range collection.Collection {
			collection.Collection[i].StreamURL += "?client_id=0f896da3d3b0d873410ff5bb29868d11"
		}

		// Encode our Collection struct as a json string and send
		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(collection)

		return
	}

	// Otherwise output the error
	utils.WriteError(utils.Error{404, "Member could not be found"}, w)
}<commit_msg>Fixed API key in playlist controller to use config<commit_after>package controllers

import (
	"io/ioutil"
	"encoding/json"
	"net/http"
	"github.com/gorilla/mux"
	"github.com/beeker1121/resounden/api/v1/utils"
	"github.com/beeker1121/resounden/api/v1/models"
)

type PlaylistController struct {
	APIKey string
}

func NewPlaylistController(apiKey string) *PlaylistController {
	return &PlaylistController{apiKey}
}

func (p *PlaylistController) Register(router *mux.Router) {
	router.HandleFunc("/api/v1/playlist/", p.getPlaylist)
	router.HandleFunc("/api/v1/playlist/{username}", p.getPlaylist)
}

func (p *PlaylistController) getPlaylist(w http.ResponseWriter, r *http.Request) {
	// Get the request variables
	vars := mux.Vars(r)

	// If the next_href param was passed to us, use that for the URL
	nextHref := r.URL.Query().Get("next_href")

	if nextHref == "" {
		nextHref = "http://api.soundcloud.com/users/" + vars["username"] + "/favorites?client_id=" + p.APIKey + "&linked_partitioning=1"
	}

	// Call the Soundcloud API
	resp, err := http.Get(nextHref)
	defer resp.Body.Close()
	if err != nil {
		utils.WriteError(utils.Error{404, "User not found"}, w)
		return
	}

	// Store the body of the response, using ioutil since it's a stream
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		utils.WriteError(utils.Error{500, "Internal server error"}, w)
		return
	}

	// Check for an error response first
	var jsonError utils.Errors

	// If we can't decode this json into our Errors struct type
	if err := json.Unmarshal(body, &jsonError); err != nil || len(jsonError.Errors) <= 0 {
		// Try decoding it into our Collection type
		var collection models.Collection

		// If we can't decode this json into our struct type
		if err := json.Unmarshal(body, &collection); err != nil {
			utils.WriteError(utils.Error{500, "Can't decode JSON into struct type"}, w)
			return
		}

		// Append the Soundcloud Client ID to stream_url for each track
		for i, _ := range collection.Collection {
			collection.Collection[i].StreamURL += "?client_id=" + p.APIKey
		}

		// Encode our Collection struct as a json string and send
		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(collection)

		return
	}

	// Otherwise output the error
	utils.WriteError(utils.Error{404, "Member could not be found"}, w)
}<|endoftext|>


<commit_before>package minion

import (
	"net/http"

	jwt "github.com/dgrijalva/jwt-go"
)

// CreateJWTToken creates a jwt token with the given secret
func CreateJWTToken(claims map[string]interface{}) (string, error) {
	_, tokenString, err := tokenAuth.Encode(claims)

	return tokenString, err
}

// Authenticator ...
func (c *Context) Authenticator(next http.Handler) http.Handler {
	return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
		unauthenticated := false

		for _, path := range c.Engine.options.UnauthenticatedRoutes {
			if req.URL.Path == path {
				unauthenticated = true
			}
		}

		if !unauthenticated {
			errResp := struct {
				Code int    `json:"status"`
				Msg  string `json:"message"`
			}{
				http.StatusUnauthorized,
				http.StatusText(http.StatusUnauthorized),
			}

			ctx := req.Context()

			if jwtErr, ok := ctx.Value("jwt.err").(error); ok {
				if jwtErr != nil {
					c.render.JSON(rw, http.StatusUnauthorized, errResp)
					return
				}
			}

			jwtToken, ok := ctx.Value("jwt").(*jwt.Token)
			if !ok || jwtToken == nil || !jwtToken.Valid {
				c.render.JSON(rw, http.StatusUnauthorized, errResp)
				return
			}
		}

		next.ServeHTTP(rw, req)
	})
}
<commit_msg>Skip jwt validation if UnauthenticatedRoutes == *<commit_after>package minion

import (
	"net/http"

	jwt "github.com/dgrijalva/jwt-go"
)

// CreateJWTToken creates a jwt token with the given secret
func CreateJWTToken(claims map[string]interface{}) (string, error) {
	_, tokenString, err := tokenAuth.Encode(claims)

	return tokenString, err
}

// Authenticator ...
func (c *Context) Authenticator(next http.Handler) http.Handler {
	return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
		if c.Engine.options.UnauthenticatedRoutes[0] != "*" {
			unauthenticated := false

			for _, path := range c.Engine.options.UnauthenticatedRoutes {
				if req.URL.Path == path {
					unauthenticated = true
				}
			}

			if !unauthenticated {
				errResp := struct {
					Code int    `json:"status"`
					Msg  string `json:"message"`
				}{
					http.StatusUnauthorized,
					http.StatusText(http.StatusUnauthorized),
				}

				ctx := req.Context()

				if jwtErr, ok := ctx.Value("jwt.err").(error); ok {
					if jwtErr != nil {
						c.render.JSON(rw, http.StatusUnauthorized, errResp)
						return
					}
				}

				jwtToken, ok := ctx.Value("jwt").(*jwt.Token)
				if !ok || jwtToken == nil || !jwtToken.Valid {
					c.render.JSON(rw, http.StatusUnauthorized, errResp)
					return
				}
			}

		}

		next.ServeHTTP(rw, req)
	})
}
<|endoftext|>


<commit_before>package flags

import (
	"fmt"
	"unicode/utf8"
)

func (p *Parser) removeGroup(group *Group) {
	for i, grp := range p.Groups {
		if grp == group {
			p.Groups = append(p.Groups[:i], p.Groups[i+1:]...)
			break
		}
	}
}

func (p *Parser) parseOption(group *Group, args []string, name string, info *Info, canarg bool, argument *string, index int) (error, int) {
	var err error

	if !info.canArgument() {
		if canarg && argument != nil {
			return newError(ErrNoArgumentForBool,
			                fmt.Sprintf("bool flag `%s' cannot have an argument", info)),
			       index
		}

		err = info.Set(nil)
	} else if canarg && (argument != nil || index < len(args)) {
		if argument == nil {
			argument = &args[index]
			index++
		}

		err = info.Set(argument)
	} else if info.OptionalArgument {
		err = info.Set(&info.Default)
	} else {
		return newError(ErrExpectedArgument,
			        fmt.Sprintf("expected argument for flag `%s'", info)),
			index
	}

	if err != nil && err.(*Error) == nil {
		err = newError(ErrMarshal,
		               fmt.Sprintf("failed to marshal argument for flag `%s': %s",
		                           info,
		                           err.Error()))
	}

	return err, index
}

func (p *Parser) parseLong(args []string, name string, argument *string, index int) (error, int) {
	for _, grp := range p.Groups {
		if info := grp.LongNames[name]; info != nil {
			return p.parseOption(grp, args, name, info, true, argument, index)
		}
	}

	return newError(ErrUnknownFlag,
		        fmt.Sprintf("unknown flag `%s'", name)),
		index
}

func (p *Parser) getShort(name rune) (*Info, *Group) {
	for _, grp := range p.Groups {
		info := grp.ShortNames[name]

		if info != nil {
			return info, grp
		}
	}

	return nil, nil
}

func (p *Parser) parseShort(args []string, name rune, islast bool, argument *string, index int) (error, int) {
	names := make([]byte, utf8.RuneLen(name))
	utf8.EncodeRune(names, name)

	info, grp := p.getShort(name)

	if info != nil {
		if info.canArgument() && !islast && !info.OptionalArgument {
			return newError(ErrExpectedArgument,
			                fmt.Sprintf("expected argument for flag `%s'", info)),
			        index
		}

		return p.parseOption(grp, args, string(names), info, islast, argument, index)
	}

	return newError(ErrUnknownFlag,
		        fmt.Sprintf("unknown flag `%s'", string(names))),
		index
}
<commit_msg>Slightly nicer error message for failed argument conversion<commit_after>package flags

import (
	"fmt"
	"unicode/utf8"
)

func (p *Parser) removeGroup(group *Group) {
	for i, grp := range p.Groups {
		if grp == group {
			p.Groups = append(p.Groups[:i], p.Groups[i+1:]...)
			break
		}
	}
}

func (p *Parser) parseOption(group *Group, args []string, name string, info *Info, canarg bool, argument *string, index int) (error, int) {
	var err error

	if !info.canArgument() {
		if canarg && argument != nil {
			return newError(ErrNoArgumentForBool,
			                fmt.Sprintf("bool flag `%s' cannot have an argument", info)),
			       index
		}

		err = info.Set(nil)
	} else if canarg && (argument != nil || index < len(args)) {
		if argument == nil {
			argument = &args[index]
			index++
		}

		err = info.Set(argument)
	} else if info.OptionalArgument {
		err = info.Set(&info.Default)
	} else {
		return newError(ErrExpectedArgument,
			        fmt.Sprintf("expected argument for flag `%s'", info)),
			index
	}

	if err != nil {
		if _, ok := err.(*Error); !ok {
			err = newError(ErrMarshal,
			               fmt.Sprintf("invalid argument for flag `%s' (expected %s)",
			                           info,
			                           info.value.Type()))
		}
	}

	return err, index
}

func (p *Parser) parseLong(args []string, name string, argument *string, index int) (error, int) {
	for _, grp := range p.Groups {
		if info := grp.LongNames[name]; info != nil {
			return p.parseOption(grp, args, name, info, true, argument, index)
		}
	}

	return newError(ErrUnknownFlag,
		        fmt.Sprintf("unknown flag `%s'", name)),
		index
}

func (p *Parser) getShort(name rune) (*Info, *Group) {
	for _, grp := range p.Groups {
		info := grp.ShortNames[name]

		if info != nil {
			return info, grp
		}
	}

	return nil, nil
}

func (p *Parser) parseShort(args []string, name rune, islast bool, argument *string, index int) (error, int) {
	names := make([]byte, utf8.RuneLen(name))
	utf8.EncodeRune(names, name)

	info, grp := p.getShort(name)

	if info != nil {
		if info.canArgument() && !islast && !info.OptionalArgument {
			return newError(ErrExpectedArgument,
			                fmt.Sprintf("expected argument for flag `%s'", info)),
			        index
		}

		return p.parseOption(grp, args, string(names), info, islast, argument, index)
	}

	return newError(ErrUnknownFlag,
		        fmt.Sprintf("unknown flag `%s'", string(names))),
		index
}
<|endoftext|>


<commit_before>// Copyright © 2015 Erik Brady <brady@dvln.org>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package lib is for general duty all purpose dvln specific library
// routines.  Third party libs (under dvln/lib/3rd) should *not* rely
// upon anything in the 'dvln/lib' dir (lib package).  Additionally
// generic standalone packages (eg: dvln/lib/<pkg> like 'dvln/lib/out')
// should also NOT depend upon anything in the dvln specific 'dvln/lib'
// location.  The 'dvln/lib' functionality can, of course, depend upon
// these third party and standalone packages.
package lib

import (
	"encoding/json"
	"fmt"

	"github.com/dvln/out"
	"github.com/dvln/toolver"
	"github.com/kr/pretty"
	globs "github.com/spf13/viper"
)

func init() {
	// Section: ConstGlobal variables to store data (default value only, no overrides)
	// - please add them alphabetically and don't reuse existing opts/vars
	globs.SetDefault("toolver", "0.0.1") // current version of the dvln tool
	globs.SetDesc("toolver", "current version of the dvln tool", globs.InternalUse, globs.ConstGlobal)
}

// DvlnToolInfo returns the version of the dvln tool and the build date for
// the binary being used and returns both, will return non-nil error on
// issues (currently only possible error will result in empty build date)
func DvlnToolInfo() (string, string, string, error) {
	toolVer := globs.GetString("toolver")
	// get the build date of the current executable
	execName, buildDate, err := toolver.ExecutableInfo()
	if err != nil {
		fmt.Errorf("Problem determining build date, error: %s", err)
	}
	return execName, toolVer, buildDate, err
}

// DvlnVerStr returns a string with the version of the dvln tool such
// that it honors verbosity levels as well as look (text/json)
func DvlnVerStr() string {
	execName, toolVer, buildDate, err := DvlnToolInfo()
	// Get current runtime settings around desired verbosity and look (format)
	look := globs.GetString("look")
	terse := globs.GetBool("terse")
	verbose := globs.GetBool("verbose")
	if err != nil {
		// err in this case is not a big deal, means no build date
		// at the debug output level it won't show up normally unless
		// debugging is on (at which point parsing isn't gonna fly
		// anyhow, so just display the error directly when debugging)
		out.Debugln(err)
	}
	type itemData struct {
		ToolVersion string `json:"toolVersion" pretty:"Version"`
		ExecName    string `json:"execName,omitempty" pretty:"Exec Name,omitempty"`
		BuildDate   string `json:"buildDate,omitempty" pretty:"Build Date,omitempty"`
	}
	items := make([]interface{}, 0, 0)
	var newItem itemData
	newItem.ToolVersion = toolVer
	if !terse {
		newItem.BuildDate = buildDate
	}
	if verbose {
		newItem.ExecName = execName
	}
	items = append(items, newItem)
	if look == "json" {
		j, err := json.Marshal(items)
		//FIXME erik: need to push out into JSON mode to get json class errs,
		//         same as below and all other errors, ie: write 'api' pkg
		//         and in json.go create api.MarshalItems(items) and that
		//         will do all the below and insert any errors into the
		//         "overall" JSON struct (rest of this should migrate to new
		//         package as well, see viper.go for similar code and we
		//         don't like having duplicate code and such)
		if err != nil {
			out.Issueln("Failed to convert \"version\" items to JSON:", err)
			return ""
		}
		var str string
		str, err = out.PrettyJSON(j)
		if err != nil {
			out.Issueln("Unable to beautify JSON output:", err)
			return ""
		}
		return str
	}
	return pretty.Sprintf("%# v", items)
}
<commit_msg>Updated to use the new api module to generate more complete JSON, more work to be done yet around errors and such<commit_after>// Copyright © 2015 Erik Brady <brady@dvln.org>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package lib is for general duty all purpose dvln specific library
// routines.  Third party libs (under dvln/lib/3rd) should *not* rely
// upon anything in the 'dvln/lib' dir (lib package).  Additionally
// generic standalone packages (eg: dvln/lib/<pkg> like 'dvln/lib/out')
// should also NOT depend upon anything in the dvln specific 'dvln/lib'
// location.  The 'dvln/lib' functionality can, of course, depend upon
// these third party and standalone packages.
package lib

import (
	"fmt"
	"os"

	"github.com/dvln/api"
	"github.com/dvln/out"
	"github.com/dvln/toolver"
	"github.com/kr/pretty"
	globs "github.com/spf13/viper"
)

const (
	toolVer = "0.0.1"
	apiVer  = "0.1"
)

func init() {
	// Section: ConstGlobal variables to store data (default value only, no overrides)
	// - please add them alphabetically and don't reuse existing opts/vars
	globs.SetDefault("toolver", toolVer) // current version of the dvln tool
	globs.SetDesc("toolver", "current version of the dvln tool", globs.InternalUse, globs.ConstGlobal)

	globs.SetDefault("apiver", apiVer) // current version of the dvln tool
	globs.SetDesc("apiver", "current version of the dvln api", globs.InternalUse, globs.ConstGlobal)
	if err := os.Setenv("PKG_API_APIVER", apiVer); err != nil {
		out.Fatalln("Failed to set PKG_API_APIVER in the environment:", err)
	}
}

// DvlnToolInfo returns the version of the dvln tool and the build date for
// the binary being used and returns both, will return non-nil error on
// issues (currently only possible error will result in empty build date)
func DvlnToolInfo() (string, string, string, string, error) {
	toolVer := globs.GetString("toolver")
	apiVer := globs.GetString("apiver")
	// get the build date of the current executable
	execName, buildDate, err := toolver.ExecutableInfo()
	if err != nil {
		fmt.Errorf("Problem determining build date, error: %s", err)
	}
	return execName, toolVer, buildDate, apiVer, err
}

// DvlnVerStr returns a string with the version of the dvln tool such
// that it honors verbosity levels as well as look (text/json)
func DvlnVerStr() string {
	execName, toolVer, buildDate, apiVer, err := DvlnToolInfo()
	// Get current runtime settings around desired verbosity and look (format)
	look := globs.GetString("look")
	terse := globs.GetBool("terse")
	verbose := globs.GetBool("verbose")
	if err != nil {
		// err in this case is not a big deal, means no build date
		// at the debug output level it won't show up normally unless
		// debugging is on (at which point parsing isn't gonna fly
		// anyhow, so just display the error directly when debugging)
		out.Debugln(err)
	}
	type itemData struct {
		ToolVersion string `json:"toolVersion" pretty:"Version"`
		APIVersion  string `json:"apiVersion,omitempty" pretty:"API Rev,omitempty"`
		BuildDate   string `json:"buildDate,omitempty" pretty:"Build Date,omitempty"`
		ExecName    string `json:"execName,omitempty" pretty:"Exec Name,omitempty"`
	}
	fields := make([]string, 0, 0)
	items := make([]interface{}, 0, 0)
	verbosity := "regular"
	var newItem itemData
	newItem.ToolVersion = toolVer
	fields = append(fields, "toolVersion")
	if !terse {
		newItem.APIVersion = apiVer
		fields = append(fields, "apiVersion")
		newItem.BuildDate = buildDate
		fields = append(fields, "buildDate")
	} else {
		verbosity = "terse"
	}
	if verbose {
		verbosity = "verbose"
		newItem.ExecName = execName
		fields = append(fields, "execName")
	}
	items = append(items, newItem)
	if look == "json" {
		return api.GetJSONString("", "dvlnVersion", "version", verbosity, fields, items)
	}
	return pretty.Sprintf("%# v", items)
}
<|endoftext|>


<commit_before>package tasks

import (
	"database/sql"
	"encoding/json"
	"errors"
	"net/http"
	"net/url"
	"time"

	"github.com/MyHomeworkSpace/api-server/mit"
	"github.com/MyHomeworkSpace/api-server/util"

	"github.com/MyHomeworkSpace/api-server/config"
)

// some classes have weird times and aren't on the catalog, so we just give up on them
var skipClasses = []string{"15.830", "24.A03"}

type subjectOffering struct {
	ID      string `json:"id"`
	Title   string `json:"title"`
	Section string `json:"section"`
	Term    string `json:"term"`

	Time  string `json:"time"`
	Place string `json:"place"`

	IsFake   bool `json:"fake"`
	IsMaster bool `json:"master"`

	IsDesign     bool `json:"design"`
	IsLab        bool `json:"lab"`
	IsLecture    bool `json:"lecture"`
	IsRecitation bool `json:"recitation"`
}

// StartImportFromMIT begins an import of the given data from the MIT Data Warehouse.
func StartImportFromMIT(source string, db *sql.DB) error {
	if source != "catalog" && source != "offerings" {
		return errors.New("tasks: invalid parameter")
	}

	taskID := "mit_" + source
	taskName := "MIT Import - " + source

	go taskWatcher(taskID, taskName, importFromMIT, source, db)
	return nil
}

func importFromMIT(lastCompletion *time.Time, source string, db *sql.DB) error {
	warehouseConfig := config.GetCurrent().MIT.Warehouse
	params := url.Values{}

	params.Add("source", source)

	// TODO: don't need this to be manually set
	params.Add("termCode", warehouseConfig.CurrentTermCode)

	requestURL := warehouseConfig.DataProxyURL + "fetch?" + params.Encode()

	response, err := http.Get(requestURL)
	if err != nil {
		return err
	}

	defer response.Body.Close()

	tx, err := db.Begin()
	if err != nil {
		return err
	}

	if source == "catalog" {
		return errors.New("tasks: catalog not supported yet")
	} else if source == "offerings" {
		offerings := []subjectOffering{}
		err = json.NewDecoder(response.Body).Decode(&offerings)
		if err != nil {
			return err
		}

		for _, offering := range offerings {
			if offering.Time == "" {
				continue
			}

			if util.StringSliceContains(skipClasses, offering.ID) {
				continue
			}

			// check that we can parse the time info
			_, err := mit.ParseAllTimeInfo(offering.Time)
			if err != nil {
				return err
			}

			_, err = tx.Exec(
				"REPLACE INTO mit_offerings(id, title, section, term, time, place, isFake, isMaster, isDesign, isLab, isLecture, isRecitation) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
				offering.ID,
				offering.Title,
				offering.Section,
				offering.Term,
				offering.Time,
				offering.Place,
				offering.IsFake,
				offering.IsMaster,
				offering.IsDesign,
				offering.IsLab,
				offering.IsLecture,
				offering.IsRecitation,
			)
			if err != nil {
				return err
			}
		}
	}

	return tx.Commit()
}
<commit_msg>add support for importing mit course catalog listings<commit_after>package tasks

import (
	"database/sql"
	"encoding/json"
	"errors"
	"net/http"
	"net/url"
	"time"

	"github.com/MyHomeworkSpace/api-server/mit"
	"github.com/MyHomeworkSpace/api-server/util"

	"github.com/MyHomeworkSpace/api-server/config"
)

// some classes have weird times and aren't on the catalog, so we just give up on them
var skipClasses = []string{"15.830", "24.A03"}

type catalogListing struct {
	ID         string `json:"id"`
	ShortTitle string `json:"short"`
	Title      string `json:"title"`

	OfferedFall   bool `bool:"fall"`
	OfferedIAP    bool `bool:"iap"`
	OfferedSpring bool `bool:"spring"`

	FallInstructors   string `json:"fallI"`
	SpringInstructors string `json:"springI"`
}

type subjectOffering struct {
	ID      string `json:"id"`
	Title   string `json:"title"`
	Section string `json:"section"`
	Term    string `json:"term"`

	Time  string `json:"time"`
	Place string `json:"place"`

	FacultyID   string `json:"facultyID"`
	FacultyName string `json:"facultyName"`

	IsFake   bool `json:"fake"`
	IsMaster bool `json:"master"`

	IsDesign     bool `json:"design"`
	IsLab        bool `json:"lab"`
	IsLecture    bool `json:"lecture"`
	IsRecitation bool `json:"recitation"`
}

// StartImportFromMIT begins an import of the given data from the MIT Data Warehouse.
func StartImportFromMIT(source string, db *sql.DB) error {
	if source != "catalog" && source != "offerings" {
		return errors.New("tasks: invalid parameter")
	}

	taskID := "mit_" + source
	taskName := "MIT Import - " + source

	go taskWatcher(taskID, taskName, importFromMIT, source, db)
	return nil
}

func importFromMIT(lastCompletion *time.Time, source string, db *sql.DB) error {
	warehouseConfig := config.GetCurrent().MIT.Warehouse
	params := url.Values{}

	params.Add("source", source)

	// TODO: don't need this to be manually set
	params.Add("termCode", warehouseConfig.CurrentTermCode)

	// TODO: remove
	params.Add("lastUpdateDate", "2018-01-01")

	requestURL := warehouseConfig.DataProxyURL + "fetch?" + params.Encode()

	response, err := http.Get(requestURL)
	if err != nil {
		return err
	}

	defer response.Body.Close()

	tx, err := db.Begin()
	if err != nil {
		return err
	}

	if source == "catalog" {
		listings := []catalogListing{}
		err = json.NewDecoder(response.Body).Decode(&listings)
		if err != nil {
			return err
		}

		for _, listing := range listings {
			_, err = tx.Exec(
				"REPLACE INTO mit_listings(id, shortTitle, title, offeredFall, offeredIAP, offeredSpring, fallInstructors, springInstructors) VALUES(?, ?, ?, ?, ?, ?, ?, ?)",
				listing.ID,
				listing.ShortTitle,
				listing.Title,
				listing.OfferedFall,
				listing.OfferedIAP,
				listing.OfferedSpring,
				listing.FallInstructors,
				listing.SpringInstructors,
			)
			if err != nil {
				return err
			}
		}
	} else if source == "offerings" {
		offerings := []subjectOffering{}
		err = json.NewDecoder(response.Body).Decode(&offerings)
		if err != nil {
			return err
		}

		for _, offering := range offerings {
			if offering.Time == "" {
				continue
			}

			if util.StringSliceContains(skipClasses, offering.ID) {
				continue
			}

			// check that we can parse the time info
			_, err := mit.ParseAllTimeInfo(offering.Time)
			if err != nil {
				return err
			}

			_, err = tx.Exec(
				"REPLACE INTO mit_offerings(id, title, section, term, time, place, facultyID, facultyName, isFake, isMaster, isDesign, isLab, isLecture, isRecitation) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
				offering.ID,
				offering.Title,
				offering.Section,
				offering.Term,
				offering.Time,
				offering.Place,
				offering.FacultyID,
				offering.FacultyName,
				offering.IsFake,
				offering.IsMaster,
				offering.IsDesign,
				offering.IsLab,
				offering.IsLecture,
				offering.IsRecitation,
			)
			if err != nil {
				return err
			}
		}
	}

	return tx.Commit()
}
<|endoftext|>


<commit_before>package http

import (
	"errors"
	"fmt"
	"io"
	"net/http"
	"os"
	"path"
	"regexp"
	"strconv"
	"strings"
)

var fileUrlMatcher = regexp.MustCompile("^/([a-z0-9]{32})$")

// HandlerConfig holds the configuration for a tus Handler.
type HandlerConfig struct {
	// Dir points to a filesystem path used by tus to store uploaded and partial
	// files. Will be created if does not exist yet. Required.
	Dir string

	// MaxSize defines how many bytes may be stored inside Dir. Exceeding this
	// limit will cause the oldest upload files to be deleted until enough space
	// is available again. Required.
	MaxSize int64

	// BasePath defines the url path used for handling uploads, e.g. "/files/".
	// Must contain a trailling "/". Requests not matching this base path will
	// cause a 404, so make sure you dispatch only appropriate requests to the
	// handler. Required.
	BasePath string
}

// NewHandler returns an initialized Handler. An error may occur if the
// config.Dir is not writable.
func NewHandler(config HandlerConfig) (*Handler, error) {
	// Ensure the data store directory exists
	if err := os.MkdirAll(config.Dir, 0777); err != nil {
		return nil, err
	}

	errChan := make(chan error)

	return &Handler{
		store:     newDataStore(config.Dir, config.MaxSize),
		config:    config,
		Error:     errChan,
		sendError: errChan,
	}, nil
}

// Handler is a http.Handler that implements tus resumable upload protocol.
type Handler struct {
	store  *dataStore
	config HandlerConfig

	// Error provides error events for logging purposes.
	Error <-chan error
	// same chan as Error, used for sending.
	sendError chan<- error
}

// ServeHTTP processes an incoming request according to the tus protocol.
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// Verify that url matches BasePath
	absPath := r.URL.Path
	if !strings.HasPrefix(absPath, h.config.BasePath) {
		err := errors.New("unknown url: " + absPath + " - does not match BasePath: " + h.config.BasePath)
		h.err(err, w, http.StatusNotFound)
		return
	}

	// example relPath results: "/", "/f81d4fae7dec11d0a76500a0c91e6bf6", etc.
	relPath := absPath[len(h.config.BasePath)-1:]

	// file creation request
	if relPath == "/" {
		if r.Method == "POST" {
			h.createFile(w, r)
			return
		}

		// handle invalid method
		w.Header().Set("Allow", "POST")
		err := errors.New(r.Method + " used against file creation url. Only POST is allowed.")
		h.err(err, w, http.StatusMethodNotAllowed)
		return
	}

	if matches := fileUrlMatcher.FindStringSubmatch(relPath); matches != nil {
		id := matches[1]
		if r.Method == "PATCH" {
			h.patchFile(w, r, id)
			return
		} else if r.Method == "HEAD" {
			h.headFile(w, r, id)
			return
		} else if r.Method == "GET" {
			h.getFile(w, r, id)
			return
		}

		// handle invalid method
		allowed := "HEAD,PATCH"
		w.Header().Set("Allow", allowed)
		err := errors.New(r.Method + " used against file creation url. Allowed: " + allowed)
		h.err(err, w, http.StatusMethodNotAllowed)
		return
	}

	// handle unknown url
	err := errors.New("unknown url: " + absPath + " - does not match file pattern")
	h.err(err, w, http.StatusNotFound)
}

func (h *Handler) createFile(w http.ResponseWriter, r *http.Request) {
	id := uid()

	finalLength, err := getPositiveIntHeader(r, "Final-Length")
	if err != nil {
		h.err(err, w, http.StatusBadRequest)
		return
	}

	// @TODO: Define meta data extension and implement it here
	// @TODO: Make max finalLength configurable, reply with error if exceeded.
	// 			  This should go into the protocol as well.
	if err := h.store.CreateFile(id, finalLength, nil); err != nil {
		h.err(err, w, http.StatusInternalServerError)
		return
	}

	w.Header().Set("Location", h.absUrl(r, "/"+id))
	w.WriteHeader(http.StatusCreated)
}

func (h *Handler) patchFile(w http.ResponseWriter, r *http.Request, id string) {
	offset, err := getPositiveIntHeader(r, "Offset")
	if err != nil {
		h.err(err, w, http.StatusBadRequest)
		return
	}

	info, err := h.store.GetInfo(id)
	if err != nil {
		h.err(err, w, http.StatusInternalServerError)
		return
	}

	if offset > info.Offset {
		err = fmt.Errorf("Offset: %d exceeds current offset: %d", offset, info.Offset)
		h.err(err, w, http.StatusForbidden)
		return
	}

	// @TODO Test offset < current offset

	err = h.store.WriteFileChunk(id, offset, r.Body)
	if err != nil {
		// @TODO handle 404 properly (goes for all h.err calls)
		h.err(err, w, http.StatusInternalServerError)
		return
	}
}

func (h *Handler) headFile(w http.ResponseWriter, r *http.Request, id string) {
	info, err := h.store.GetInfo(id)
	if err != nil {
		h.err(err, w, http.StatusInternalServerError)
		return
	}

	w.Header().Set("Offset", fmt.Sprintf("%d", info.Offset))
}

// GET requests on files aren't part of the protocol yet,
// but it is implemented here anyway for the demo. It still lacks the meta data
// extension in order to send the proper content type header.
func (h *Handler) getFile(w http.ResponseWriter, r *http.Request, fileId string) {
	info, err := h.store.GetInfo(fileId)
	if os.IsNotExist(err) {
		h.err(err, w, http.StatusNotFound)
		return
	}
	if err != nil {
		h.err(err, w, http.StatusInternalServerError)
		return
	}

	data, err := h.store.ReadFile(fileId)
	if os.IsNotExist(err) {
		h.err(err, w, http.StatusNotFound)
		return
	}
	if err != nil {
		h.err(err, w, http.StatusInternalServerError)
		return
	}

	defer data.Close()

	w.Header().Set("Offset", strconv.FormatInt(info.Offset, 10))

	// @TODO: Once the meta extension is done, send the proper content type here
	//w.Header().Set("Content-Type", info.Meta.ContentType)

	w.Header().Set("Content-Length", strconv.FormatInt(info.FinalLength, 10))

	if _, err := io.CopyN(w, data, info.FinalLength); err != nil {
		return
	}
}

func getPositiveIntHeader(r *http.Request, key string) (int64, error) {
	val := r.Header.Get(key)
	if val == "" {
		return 0, errors.New(key + " header must not be empty")
	}

	intVal, err := strconv.ParseInt(val, 10, 64)
	if err != nil {
		return 0, errors.New("invalid " + key + " header: " + err.Error())
	} else if intVal < 0 {
		return 0, errors.New(key + " header must be > 0")
	}
	return intVal, nil
}

// absUrl turn a relPath (e.g. "/foo") into an absolute url (e.g.
// "http://example.com/foo").
//
// @TODO: Look at r.TLS to determine the url scheme.
// @TODO: Make url prefix user configurable (optional) to deal with reverse
// 				proxies. This could be done by turning BasePath into BaseURL that
//				that could be relative or absolute.
func (h *Handler) absUrl(r *http.Request, relPath string) string {
	return "http://" + r.Host + path.Clean(h.config.BasePath+relPath)
}

// err sends a http error response and publishes to the Error channel.
func (h *Handler) err(err error, w http.ResponseWriter, status int) {
	w.WriteHeader(status)
	io.WriteString(w, err.Error()+"\n")

	// non-blocking send
	select {
	case h.sendError <- err:
	default:
	}
}
<commit_msg>Replace 500 with 404 and Don't return body for HEAD request<commit_after>package http

import (
	"errors"
	"fmt"
	"io"
	"net/http"
	"os"
	"path"
	"regexp"
	"strconv"
	"strings"
)

var fileUrlMatcher = regexp.MustCompile("^/([a-z0-9]{32})$")

// HandlerConfig holds the configuration for a tus Handler.
type HandlerConfig struct {
	// Dir points to a filesystem path used by tus to store uploaded and partial
	// files. Will be created if does not exist yet. Required.
	Dir string

	// MaxSize defines how many bytes may be stored inside Dir. Exceeding this
	// limit will cause the oldest upload files to be deleted until enough space
	// is available again. Required.
	MaxSize int64

	// BasePath defines the url path used for handling uploads, e.g. "/files/".
	// Must contain a trailling "/". Requests not matching this base path will
	// cause a 404, so make sure you dispatch only appropriate requests to the
	// handler. Required.
	BasePath string
}

// NewHandler returns an initialized Handler. An error may occur if the
// config.Dir is not writable.
func NewHandler(config HandlerConfig) (*Handler, error) {
	// Ensure the data store directory exists
	if err := os.MkdirAll(config.Dir, 0777); err != nil {
		return nil, err
	}

	errChan := make(chan error)

	return &Handler{
		store:     newDataStore(config.Dir, config.MaxSize),
		config:    config,
		Error:     errChan,
		sendError: errChan,
	}, nil
}

// Handler is a http.Handler that implements tus resumable upload protocol.
type Handler struct {
	store  *dataStore
	config HandlerConfig

	// Error provides error events for logging purposes.
	Error <-chan error
	// same chan as Error, used for sending.
	sendError chan<- error
}

// ServeHTTP processes an incoming request according to the tus protocol.
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// Verify that url matches BasePath
	absPath := r.URL.Path
	if !strings.HasPrefix(absPath, h.config.BasePath) {
		err := errors.New("unknown url: " + absPath + " - does not match BasePath: " + h.config.BasePath)
		h.err(err, w, http.StatusNotFound)
		return
	}

	// example relPath results: "/", "/f81d4fae7dec11d0a76500a0c91e6bf6", etc.
	relPath := absPath[len(h.config.BasePath)-1:]

	// file creation request
	if relPath == "/" {
		if r.Method == "POST" {
			h.createFile(w, r)
			return
		}

		// handle invalid method
		w.Header().Set("Allow", "POST")
		err := errors.New(r.Method + " used against file creation url. Only POST is allowed.")
		h.err(err, w, http.StatusMethodNotAllowed)
		return
	}

	if matches := fileUrlMatcher.FindStringSubmatch(relPath); matches != nil {
		id := matches[1]
		if r.Method == "PATCH" {
			h.patchFile(w, r, id)
			return
		} else if r.Method == "HEAD" {
			h.headFile(w, r, id)
			return
		} else if r.Method == "GET" {
			h.getFile(w, r, id)
			return
		}

		// handle invalid method
		allowed := "HEAD,PATCH"
		w.Header().Set("Allow", allowed)
		err := errors.New(r.Method + " used against file creation url. Allowed: " + allowed)
		h.err(err, w, http.StatusMethodNotAllowed)
		return
	}

	// handle unknown url
	err := errors.New("unknown url: " + absPath + " - does not match file pattern")
	h.err(err, w, http.StatusNotFound)
}

func (h *Handler) createFile(w http.ResponseWriter, r *http.Request) {
	id := uid()

	finalLength, err := getPositiveIntHeader(r, "Final-Length")
	if err != nil {
		h.err(err, w, http.StatusBadRequest)
		return
	}

	// @TODO: Define meta data extension and implement it here
	// @TODO: Make max finalLength configurable, reply with error if exceeded.
	// 			  This should go into the protocol as well.
	if err := h.store.CreateFile(id, finalLength, nil); err != nil {
		h.err(err, w, http.StatusInternalServerError)
		return
	}

	w.Header().Set("Location", h.absUrl(r, "/"+id))
	w.WriteHeader(http.StatusCreated)
}

func (h *Handler) patchFile(w http.ResponseWriter, r *http.Request, id string) {
	offset, err := getPositiveIntHeader(r, "Offset")
	if err != nil {
		h.err(err, w, http.StatusBadRequest)
		return
	}

	info, err := h.store.GetInfo(id)
	if err != nil {
		h.err(err, w, http.StatusInternalServerError)
		return
	}

	if offset > info.Offset {
		err = fmt.Errorf("Offset: %d exceeds current offset: %d", offset, info.Offset)
		h.err(err, w, http.StatusForbidden)
		return
	}

	// @TODO Test offset < current offset

	err = h.store.WriteFileChunk(id, offset, r.Body)
	if err != nil {
		// @TODO handle 404 properly (goes for all h.err calls)
		h.err(err, w, http.StatusInternalServerError)
		return
	}
}

func (h *Handler) headFile(w http.ResponseWriter, r *http.Request, id string) {
	info, err := h.store.GetInfo(id)
	if err != nil {
		w.Header().Set("Content-Length", "0")
		w.WriteHeader(http.StatusNotFound)
		return
	}

	w.Header().Set("Offset", fmt.Sprintf("%d", info.Offset))
}

// GET requests on files aren't part of the protocol yet,
// but it is implemented here anyway for the demo. It still lacks the meta data
// extension in order to send the proper content type header.
func (h *Handler) getFile(w http.ResponseWriter, r *http.Request, fileId string) {
	info, err := h.store.GetInfo(fileId)
	if os.IsNotExist(err) {
		h.err(err, w, http.StatusNotFound)
		return
	}
	if err != nil {
		h.err(err, w, http.StatusInternalServerError)
		return
	}

	data, err := h.store.ReadFile(fileId)
	if os.IsNotExist(err) {
		h.err(err, w, http.StatusNotFound)
		return
	}
	if err != nil {
		h.err(err, w, http.StatusInternalServerError)
		return
	}

	defer data.Close()

	w.Header().Set("Offset", strconv.FormatInt(info.Offset, 10))

	// @TODO: Once the meta extension is done, send the proper content type here
	//w.Header().Set("Content-Type", info.Meta.ContentType)

	w.Header().Set("Content-Length", strconv.FormatInt(info.FinalLength, 10))

	if _, err := io.CopyN(w, data, info.FinalLength); err != nil {
		return
	}
}

func getPositiveIntHeader(r *http.Request, key string) (int64, error) {
	val := r.Header.Get(key)
	if val == "" {
		return 0, errors.New(key + " header must not be empty")
	}

	intVal, err := strconv.ParseInt(val, 10, 64)
	if err != nil {
		return 0, errors.New("invalid " + key + " header: " + err.Error())
	} else if intVal < 0 {
		return 0, errors.New(key + " header must be > 0")
	}
	return intVal, nil
}

// absUrl turn a relPath (e.g. "/foo") into an absolute url (e.g.
// "http://example.com/foo").
//
// @TODO: Look at r.TLS to determine the url scheme.
// @TODO: Make url prefix user configurable (optional) to deal with reverse
// 				proxies. This could be done by turning BasePath into BaseURL that
//				that could be relative or absolute.
func (h *Handler) absUrl(r *http.Request, relPath string) string {
	return "http://" + r.Host + path.Clean(h.config.BasePath+relPath)
}

// err sends a http error response and publishes to the Error channel.
func (h *Handler) err(err error, w http.ResponseWriter, status int) {
	w.WriteHeader(status)
	io.WriteString(w, err.Error()+"\n")

	// non-blocking send
	select {
	case h.sendError <- err:
	default:
	}
}
<|endoftext|>


<commit_before>package dev

import (
	"bufio"
	"fmt"
	"io"
	"net"
	"os"
	"os/exec"
	"regexp"
	"strings"
	"sync"
	"time"

	"github.com/moomerman/phx-dev/multiproxy"
	"github.com/puma/puma-dev/homedir"
	"github.com/vektra/errors"
)

const appsPath = "~/.phx-dev"

var apps map[string]*App
var lock sync.Mutex

type App struct {
	Host    string
	Port    string
	Link    string
	Dir     string
	Command *exec.Cmd

	proxy  *multiproxy.MultiProxy
	stdout io.Reader
}

func (a *App) Start() error {
	path := homedir.MustExpand(appsPath) + "/" + a.Host
	stat, err := os.Stat(path)
	if err != nil {
		return err
	}

	dir, err := os.Readlink(path)
	if err != nil {
		return err
	}

	if stat.IsDir() {
		a.Link = path
		a.Dir = dir
	} else {
		return errors.New("unknown app")
	}

	return a.launch()
}

const executionShell = `exec bash -c '
cd %s
exec mix do deps.get, phx.server'
`

func (a *App) launch() error {
	shell := os.Getenv("SHELL")
	addr := findAvailableAddr()
	_, port, err := net.SplitHostPort(addr)
	a.Port = port

	cmd := exec.Command(shell, "-l", "-i", "-c",
		fmt.Sprintf(executionShell, a.Dir))

	cmd.Dir = a.Dir

	cmd.Env = os.Environ()
	cmd.Env = append(cmd.Env,
		fmt.Sprintf("PHX_PORT=%s", a.Port),
	)

	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return err
	}

	a.stdout = stdout
	cmd.Stderr = cmd.Stdout

	err = cmd.Start()
	if err != nil {
		return errors.Context(err, "starting app")
	}

	a.Command = cmd
	a.proxy = multiproxy.NewProxy("http://127.0.0.1:"+a.Port, a.Host)

	time.Sleep(5 * time.Second)

	go a.tail()

	return nil
}

func (a *App) tail() error {
	c := make(chan error)

	go func() {
		r := bufio.NewReader(a.stdout)

		for {
			line, err := r.ReadString('\n')
			if line != "" {
				fmt.Fprintf(os.Stdout, "  [app] %s:%s[%d]: %s", a.Host, a.Port, a.Command.Process.Pid, line)
				mustRestart, _ := regexp.Compile("You must restart your server")
				if mustRestart.MatchString(line) {
					c <- errors.New("Restart required")
					return
				}
			}

			if err != nil {
				c <- err
				return
			}
		}
	}()

	var err error

	select {
	case err = <-c:
		err = errors.Context(err, "stdout/stderr closed")
	}

	a.Command.Process.Kill()
	a.Command.Wait()

	lock.Lock()
	defer lock.Unlock()
	delete(apps, a.Host)

	fmt.Printf("* App '%s' shutdown and cleaned up\n", a.Host)

	return err
}

func findAppForHost(host string) (*App, error) {
	lock.Lock()
	defer lock.Unlock()

	hostParts := strings.Split(host, ":")
	host = hostParts[0]

	if apps == nil {
		apps = make(map[string]*App)
	}

	app := apps[host]
	if app != nil {
		return app, nil
	}

	fmt.Println("[app] attempting to start app for host", host)

	app = &App{
		Host: host,
	}

	err := app.Start()
	if err != nil {
		fmt.Println("[app] error starting app for host", host, err)
		return nil, err
	}

	fmt.Println("[app] created app for host", host, app.Port)
	// TODO: apps should be keyed by Dir not host as you might have multiple
	// hosts pointing to the same app
	apps[host] = app
	return app, nil
}

func findAvailableAddr() string {
	l, _ := net.Listen("tcp", ":0")
	l.Close()
	return l.Addr().String()
}
<commit_msg>Kill idle apps after 1 hour<commit_after>package dev

import (
	"bufio"
	"fmt"
	"io"
	"net"
	"os"
	"os/exec"
	"regexp"
	"strings"
	"sync"
	"time"

	"github.com/moomerman/phx-dev/multiproxy"
	"github.com/puma/puma-dev/homedir"
	"github.com/vektra/errors"
)

const appsPath = "~/.phx-dev"

var apps map[string]*App
var lock sync.Mutex

type App struct {
	Host    string
	Port    string
	Link    string
	Dir     string
	Command *exec.Cmd

	proxy    *multiproxy.MultiProxy
	stdout   io.Reader
	lastUsed time.Time
}

func (a *App) Start() error {
	path := homedir.MustExpand(appsPath) + "/" + a.Host
	stat, err := os.Stat(path)
	if err != nil {
		return err
	}

	dir, err := os.Readlink(path)
	if err != nil {
		return err
	}

	if stat.IsDir() {
		a.Link = path
		a.Dir = dir
	} else {
		return errors.New("unknown app")
	}

	return a.launch()
}

func (a *App) Stop(reason string, e error) error {
	fmt.Printf("! Stopping '%s' (%d) %s %s\n", a.Host, a.Command.Process.Pid, reason, e)
	lock.Lock()
	defer lock.Unlock()
	delete(apps, a.Host)

	err := a.Command.Process.Kill()
	if err != nil {
		fmt.Printf("! Error trying to stop %s: %s", a.Host, err)
		return err
	}

	a.Command.Wait()

	fmt.Printf("* App '%s' shutdown and cleaned up\n", a.Host)
	return nil
}

const executionShell = `exec bash -c '
cd %s
exec mix do deps.get, phx.server'
`

func (a *App) launch() error {
	shell := os.Getenv("SHELL")
	addr := findAvailableAddr()
	_, port, err := net.SplitHostPort(addr)
	a.Port = port

	cmd := exec.Command(shell, "-l", "-i", "-c",
		fmt.Sprintf(executionShell, a.Dir))

	cmd.Dir = a.Dir

	cmd.Env = os.Environ()
	cmd.Env = append(cmd.Env,
		fmt.Sprintf("PHX_PORT=%s", a.Port),
	)

	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return err
	}

	a.stdout = stdout
	cmd.Stderr = cmd.Stdout

	err = cmd.Start()
	if err != nil {
		return errors.Context(err, "starting app")
	}

	a.Command = cmd
	a.proxy = multiproxy.NewProxy("http://127.0.0.1:"+a.Port, a.Host)

	time.Sleep(5 * time.Second)

	go a.tail()
	go a.idleMonitor()

	return nil
}

func (a *App) tail() error {
	c := make(chan error)

	go func() {
		r := bufio.NewReader(a.stdout)

		for {
			line, err := r.ReadString('\n')
			if line != "" {
				fmt.Fprintf(os.Stdout, "  [app] %s:%s[%d]: %s", a.Host, a.Port, a.Command.Process.Pid, line)
				mustRestart, _ := regexp.Compile("You must restart your server")
				if mustRestart.MatchString(line) {
					c <- errors.New("Restart required")
					return
				}
			}

			if err != nil {
				c <- err
				return
			}
		}
	}()

	var err error

	select {
	case err = <-c:
		err = errors.Context(err, "stdout/stderr closed")
	}

	a.Stop("see error", err)

	return err
}

func (a *App) idleMonitor() error {
	ticker := time.NewTicker(30 * time.Second)
	defer ticker.Stop()

	for {
		select {
		case <-ticker.C:
			if a.idle() {
				a.Stop("app is idle", nil)
				return nil
			}
		}
	}

	return nil
}

func (a *App) idle() bool {
	diff := time.Since(a.lastUsed)
	if diff > 60*60*time.Second {
		lock.Lock()
		defer lock.Unlock()
		return true
	}

	return false
}

func findAppForHost(host string) (*App, error) {
	lock.Lock()
	defer lock.Unlock()

	hostParts := strings.Split(host, ":")
	host = hostParts[0]

	if apps == nil {
		apps = make(map[string]*App)
	}

	app := apps[host]
	if app != nil {
		app.lastUsed = time.Now()
		return app, nil
	}

	fmt.Println("[app] attempting to start app for host", host)

	app = &App{
		Host:     host,
		lastUsed: time.Now(),
	}

	err := app.Start()
	if err != nil {
		fmt.Println("[app] error starting app for host", host, err)
		return nil, err
	}

	fmt.Println("[app] created app for host", host, app.Port)
	// TODO: apps should be keyed by Dir not host as you might have multiple
	// hosts pointing to the same app
	apps[host] = app
	return app, nil
}

func findAvailableAddr() string {
	l, _ := net.Listen("tcp", ":0")
	l.Close()
	return l.Addr().String()
}
<|endoftext|>


<commit_before>package junos

import (
	"encoding/xml"
)

// DeviceList holds a []Device slice of every device within Space.
type DeviceList struct {
	XMLName xml.Name `xml:"devices"`
	Devices []Device `xml:"device"`
}

// Device holds all the information about each device within Space.
type Device struct {
	ID        string `xml:"key,attr"`
	Family    string `xml:"deviceFamily"`
	Version   string `xml:"OSVersion"`
	Platform  string `xml:"platform"`
	Serial    string `xml:"serialNumber"`
	IPAddress string `xml:"ipAddr"`
	Name      string `xml:"name"`
}

// Devices queries the Junos Space server and returns all of the information
// about each device that is managed by Space.
func (s *JunosSpace) Devices() (*DeviceList, error) {
	var devices DeviceList
	data, err := s.APIRequest("device-management/devices")
	if err != nil {
		return nil, err
	}

	err = xml.Unmarshal(data, &devices)
	if err != nil {
		return nil, err
	}

	return &devices, nil
}
<commit_msg>Added functions to add and remove devices from Junos Space<commit_after>package junos

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

// DeviceList holds a []Device slice of every device within Space.
type DeviceList struct {
	XMLName xml.Name `xml:"devices"`
	Devices []Device `xml:"device"`
}

// Device holds all the information about each device within Space.
type Device struct {
	ID        int `xml:"key,attr"`
	Family    string `xml:"deviceFamily"`
	Version   string `xml:"OSVersion"`
	Platform  string `xml:"platform"`
	Serial    string `xml:"serialNumber"`
	IPAddress string `xml:"ipAddr"`
	Name      string `xml:"name"`
}

// AddDevice adds a new managed device to Junos Space, and returns the Job ID.
func (s *JunosSpace) AddDevice(host, user, password string) error {
    ipRegex := regexp.MustCompile(`(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})`)
    inputXML := "<discover-devices>"

    if ipRegex.MatchString(host) {
        inputXML += fmt.Sprintf("<ipAddressDiscoveryTarget><ipAddress>%s</ipAddress></ipAddressDiscoveryTarget>", host)
    } else {
        inputXML += fmt.Sprintf("<hostNameDiscoveryTarget><hostName>%s</hostName></hostNameDiscoveryTarget>", host)
    }

    inputXML += fmt.Sprintf("<sshCredential><userName>%s</userName><password>%s</password></sshCredential>", user, password)
    inputXML += "<manageDiscoveredSystemsFlag>true</manageDiscoveredSystemsFlag><usePing>true</usePing></discover-devices>"

    err := s.APIPost("device-management/discover-devices", inputXML, "discover-devices")
    if err != nil {
        return err
    }

    return nil
}

// Devices queries the Junos Space server and returns all of the information
// about each device that is managed by Space.
func (s *JunosSpace) Devices() (*DeviceList, error) {
	var devices DeviceList
	data, err := s.APIRequest("device-management/devices")
	if err != nil {
		return nil, err
	}

	err = xml.Unmarshal(data, &devices)
	if err != nil {
		return nil, err
	}

	return &devices, nil
}

// RemoveDevice removes a device from Junos Space.
func (s *JunosSpace) RemoveDevice(id int) error {
    err := s.APIDelete(fmt.Sprintf("device-management/devices/%d", id))
    if err != nil {
        return err
    }

    return nil
}
<|endoftext|>


<commit_before>/*******************************************************************************
 * This is very experimental code and probably a long way from perfect or
 * ideal.  Please provide feed back on areas that would improve performance
 *
 */

// Package dgvoice provides opus encoding and audio file playback for the
// Discordgo package.
package dgvoice

import (
	"bufio"
	"encoding/binary"
	"fmt"
	"io"
	"os"
	"os/exec"
	"strconv"
	"sync"

	"github.com/bwmarrin/discordgo"
	"layeh.com/gopus"
)

// NOTE: This API is not final and these are likely to change.

// Technically the below settings can be adjusted however that poses
// a lot of other problems that are not handled well at this time.
// These below values seem to provide the best overall performance
const (
	channels  int = 2                   // 1 for mono, 2 for stereo
	frameRate int = 48000               // audio sampling rate
	frameSize int = 960                 // uint16 size of each audio frame
	maxBytes  int = (frameSize * 2) * 2 // max size of opus data
)

var (
	speakers    map[uint32]*gopus.Decoder
	opusEncoder *gopus.Encoder
	mu          sync.Mutex
)

// OnError gets called by dgvoice when an error is encountered.
// By default logs to STDERR
var OnError = func(str string, err error) {
	prefix := "dgVoice: " + str

	if err != nil {
		os.Stderr.WriteString(prefix + ", " + err.Error())
	} else {
		os.Stderr.WriteString(prefix)
	}
}

// SendPCM will receive on the provied channel encode
// received PCM data into Opus then send that to Discordgo
func SendPCM(v *discordgo.VoiceConnection, pcm <-chan []int16) {
	if pcm == nil {
		return
	}

	var err error

	opusEncoder, err = gopus.NewEncoder(frameRate, channels, gopus.Audio)

	if err != nil {
		OnError("NewEncoder Error", err)
		return
	}

	for {

		// read pcm from chan, exit if channel is closed.
		recv, ok := <-pcm
		if !ok {
			OnError("PCM Channel closed.", nil)
			return
		}

		// try encoding pcm frame with Opus
		opus, err := opusEncoder.Encode(recv, frameSize, maxBytes)
		if err != nil {
			OnError("Encoding Error", err)
			return
		}

		if v.Ready == false || v.OpusSend == nil {
			// OnError(fmt.Sprintf("Discordgo not ready for opus packets. %+v : %+v", v.Ready, v.OpusSend), nil)
			// Sending errors here might not be suited
			return
		}
		// send encoded opus data to the sendOpus channel
		v.OpusSend <- opus
	}
}

// ReceivePCM will receive on the the Discordgo OpusRecv channel and decode
// the opus audio into PCM then send it on the provided channel.
func ReceivePCM(v *discordgo.VoiceConnection, c chan *discordgo.Packet) {
	if c == nil {
		return
	}

	var err error

	for {
		if v.Ready == false || v.OpusRecv == nil {
			OnError(fmt.Sprintf("Discordgo not to receive opus packets. %+v : %+v", v.Ready, v.OpusSend), nil)
			return
		}

		p, ok := <-v.OpusRecv
		if !ok {
			return
		}

		if speakers == nil {
			speakers = make(map[uint32]*gopus.Decoder)
		}

		_, ok = speakers[p.SSRC]
		if !ok {
			speakers[p.SSRC], err = gopus.NewDecoder(48000, 2)
			if err != nil {
				OnError("error creating opus decoder", err)
				continue
			}
		}

		p.PCM, err = speakers[p.SSRC].Decode(p.Opus, 960, false)
		if err != nil {
			OnError("Error decoding opus data", err)
			continue
		}

		c <- p
	}
}

// PlayAudioFile will play the given filename to the already connected
// Discord voice server/channel.  voice websocket and udp socket
// must already be setup before this will work.
func PlayAudioFile(v *discordgo.VoiceConnection, filename string, stop <-chan bool) {

	// Create a shell command "object" to run.
	run := exec.Command("ffmpeg", "-i", filename, "-f", "s16le", "-ar", strconv.Itoa(frameRate), "-ac", strconv.Itoa(channels), "pipe:1")
	ffmpegout, err := run.StdoutPipe()
	if err != nil {
		OnError("StdoutPipe Error", err)
		return
	}

	ffmpegbuf := bufio.NewReaderSize(ffmpegout, 16384)

	// Starts the ffmpeg command
	err = run.Start()
	if err != nil {
		OnError("RunStart Error", err)
		return
	}

	go func() {
		<-stop
		err = run.Process.Kill()
	}()

	// Send "speaking" packet over the voice websocket
	v.Speaking(true)

	// Send not "speaking" packet over the websocket when we finish
	defer v.Speaking(false)

	send := make(chan []int16, 2)
	defer close(send)

	close := make(chan bool)
	go func() {
		SendPCM(v, send)
		close <- true
	}()

	for {
		// read data from ffmpeg stdout
		audiobuf := make([]int16, frameSize*channels)
		err = binary.Read(ffmpegbuf, binary.LittleEndian, &audiobuf)
		if err == io.EOF || err == io.ErrUnexpectedEOF {
			return
		}
		if err != nil {
			OnError("error reading from ffmpeg stdout", err)
			return
		}

		// Send received PCM to the sendPCM channel
		select {
		case send <- audiobuf:
		case <-close:
			return
		}
	}
}
<commit_msg>Error checking<commit_after>/*******************************************************************************
 * This is very experimental code and probably a long way from perfect or
 * ideal.  Please provide feed back on areas that would improve performance
 *
 */

// Package dgvoice provides opus encoding and audio file playback for the
// Discordgo package.
package dgvoice

import (
	"bufio"
	"encoding/binary"
	"fmt"
	"io"
	"os"
	"os/exec"
	"strconv"
	"sync"

	"github.com/bwmarrin/discordgo"
	"layeh.com/gopus"
)

// NOTE: This API is not final and these are likely to change.

// Technically the below settings can be adjusted however that poses
// a lot of other problems that are not handled well at this time.
// These below values seem to provide the best overall performance
const (
	channels  int = 2                   // 1 for mono, 2 for stereo
	frameRate int = 48000               // audio sampling rate
	frameSize int = 960                 // uint16 size of each audio frame
	maxBytes  int = (frameSize * 2) * 2 // max size of opus data
)

var (
	speakers    map[uint32]*gopus.Decoder
	opusEncoder *gopus.Encoder
	mu          sync.Mutex
)

// OnError gets called by dgvoice when an error is encountered.
// By default logs to STDERR
var OnError = func(str string, err error) {
	prefix := "dgVoice: " + str

	if err != nil {
		os.Stderr.WriteString(prefix + ", " + err.Error())
	} else {
		os.Stderr.WriteString(prefix)
	}
}

// SendPCM will receive on the provied channel encode
// received PCM data into Opus then send that to Discordgo
func SendPCM(v *discordgo.VoiceConnection, pcm <-chan []int16) {
	if pcm == nil {
		return
	}

	var err error

	opusEncoder, err = gopus.NewEncoder(frameRate, channels, gopus.Audio)

	if err != nil {
		OnError("NewEncoder Error", err)
		return
	}

	for {

		// read pcm from chan, exit if channel is closed.
		recv, ok := <-pcm
		if !ok {
			OnError("PCM Channel closed.", nil)
			return
		}

		// try encoding pcm frame with Opus
		opus, err := opusEncoder.Encode(recv, frameSize, maxBytes)
		if err != nil {
			OnError("Encoding Error", err)
			return
		}

		if v.Ready == false || v.OpusSend == nil {
			// OnError(fmt.Sprintf("Discordgo not ready for opus packets. %+v : %+v", v.Ready, v.OpusSend), nil)
			// Sending errors here might not be suited
			return
		}
		// send encoded opus data to the sendOpus channel
		v.OpusSend <- opus
	}
}

// ReceivePCM will receive on the the Discordgo OpusRecv channel and decode
// the opus audio into PCM then send it on the provided channel.
func ReceivePCM(v *discordgo.VoiceConnection, c chan *discordgo.Packet) {
	if c == nil {
		return
	}

	var err error

	for {
		if v.Ready == false || v.OpusRecv == nil {
			OnError(fmt.Sprintf("Discordgo not to receive opus packets. %+v : %+v", v.Ready, v.OpusSend), nil)
			return
		}

		p, ok := <-v.OpusRecv
		if !ok {
			return
		}

		if speakers == nil {
			speakers = make(map[uint32]*gopus.Decoder)
		}

		_, ok = speakers[p.SSRC]
		if !ok {
			speakers[p.SSRC], err = gopus.NewDecoder(48000, 2)
			if err != nil {
				OnError("error creating opus decoder", err)
				continue
			}
		}

		p.PCM, err = speakers[p.SSRC].Decode(p.Opus, 960, false)
		if err != nil {
			OnError("Error decoding opus data", err)
			continue
		}

		c <- p
	}
}

// PlayAudioFile will play the given filename to the already connected
// Discord voice server/channel.  voice websocket and udp socket
// must already be setup before this will work.
func PlayAudioFile(v *discordgo.VoiceConnection, filename string, stop <-chan bool) {

	// Create a shell command "object" to run.
	run := exec.Command("ffmpeg", "-i", filename, "-f", "s16le", "-ar", strconv.Itoa(frameRate), "-ac", strconv.Itoa(channels), "pipe:1")
	ffmpegout, err := run.StdoutPipe()
	if err != nil {
		OnError("StdoutPipe Error", err)
		return
	}

	ffmpegbuf := bufio.NewReaderSize(ffmpegout, 16384)

	// Starts the ffmpeg command
	err = run.Start()
	if err != nil {
		OnError("RunStart Error", err)
		return
	}

	go func() {
		<-stop
		err = run.Process.Kill()
	}()

	// Send "speaking" packet over the voice websocket
	err = v.Speaking(true)
	if err != nil {
		OnError("Couldn't set speaking", err)
	}

	// Send not "speaking" packet over the websocket when we finish
	defer func() {
		err := v.Speaking(false)
		if err != nil {
			OnError("Couldn't stop speaking", err)
		}
	}()

	send := make(chan []int16, 2)
	defer close(send)

	close := make(chan bool)
	go func() {
		SendPCM(v, send)
		close <- true
	}()

	for {
		// read data from ffmpeg stdout
		audiobuf := make([]int16, frameSize*channels)
		err = binary.Read(ffmpegbuf, binary.LittleEndian, &audiobuf)
		if err == io.EOF || err == io.ErrUnexpectedEOF {
			return
		}
		if err != nil {
			OnError("error reading from ffmpeg stdout", err)
			return
		}

		// Send received PCM to the sendPCM channel
		select {
		case send <- audiobuf:
		case <-close:
			return
		}
	}
}
<|endoftext|>


<commit_before>package dht

import (
	"bufio"
	"context"
	"fmt"
	"io"
	"sync"
	"time"

	ggio "github.com/gogo/protobuf/io"
	ctxio "github.com/jbenet/go-context/io"
	pb "github.com/libp2p/go-libp2p-kad-dht/pb"
	inet "github.com/libp2p/go-libp2p-net"
	peer "github.com/libp2p/go-libp2p-peer"
)

var dhtReadMessageTimeout = time.Minute
var ErrReadTimeout = fmt.Errorf("timed out reading response")

type bufferedWriteCloser interface {
	ggio.WriteCloser
	Flush() error
}

// The Protobuf writer performs multiple small writes when writing a message.
// We need to buffer those writes, to make sure that we're not sending a new
// packet for every single write.
type bufferedDelimitedWriter struct {
	*bufio.Writer
	ggio.WriteCloser
}

func newBufferedDelimitedWriter(str io.Writer) bufferedWriteCloser {
	w := bufio.NewWriter(str)
	return &bufferedDelimitedWriter{
		Writer:      w,
		WriteCloser: ggio.NewDelimitedWriter(w),
	}
}

func (w *bufferedDelimitedWriter) Flush() error {
	return w.Writer.Flush()
}

// handleNewStream implements the inet.StreamHandler
func (dht *IpfsDHT) handleNewStream(s inet.Stream) {
	go dht.handleNewMessage(s)
}

func (dht *IpfsDHT) handleNewMessage(s inet.Stream) {
	ctx := dht.Context()
	cr := ctxio.NewReader(ctx, s) // ok to use. we defer close stream in this func
	cw := ctxio.NewWriter(ctx, s) // ok to use. we defer close stream in this func
	r := ggio.NewDelimitedReader(cr, inet.MessageSizeMax)
	w := newBufferedDelimitedWriter(cw)
	mPeer := s.Conn().RemotePeer()

	for {
		// receive msg
		pmes := new(pb.Message)
		switch err := r.ReadMsg(pmes); err {
		case io.EOF:
			s.Close()
			return
		case nil:
		default:
			s.Reset()
			logger.Debugf("Error unmarshaling data: %s", err)
			return
		}

		// update the peer (on valid msgs only)
		dht.updateFromMessage(ctx, mPeer, pmes)

		// get handler for this msg type.
		handler := dht.handlerForMsgType(pmes.GetType())
		if handler == nil {
			s.Reset()
			logger.Debug("got back nil handler from handlerForMsgType")
			return
		}

		// dispatch handler.
		rpmes, err := handler(ctx, mPeer, pmes)
		if err != nil {
			s.Reset()
			logger.Debugf("handle message error: %s", err)
			return
		}

		// if nil response, return it before serializing
		if rpmes == nil {
			logger.Debug("got back nil response from request")
			continue
		}

		// send out response msg
		err = w.WriteMsg(rpmes)
		if err == nil {
			err = w.Flush()
		}
		if err != nil {
			s.Reset()
			logger.Debugf("send response error: %s", err)
			return
		}
	}
}

// sendRequest sends out a request, but also makes sure to
// measure the RTT for latency measurements.
func (dht *IpfsDHT) sendRequest(ctx context.Context, p peer.ID, pmes *pb.Message) (*pb.Message, error) {

	ms, err := dht.messageSenderForPeer(p)
	if err != nil {
		return nil, err
	}

	start := time.Now()

	rpmes, err := ms.SendRequest(ctx, pmes)
	if err != nil {
		return nil, err
	}

	// update the peer (on valid msgs only)
	dht.updateFromMessage(ctx, p, rpmes)

	dht.peerstore.RecordLatency(p, time.Since(start))
	logger.Event(ctx, "dhtReceivedMessage", dht.self, p, rpmes)
	return rpmes, nil
}

// sendMessage sends out a message
func (dht *IpfsDHT) sendMessage(ctx context.Context, p peer.ID, pmes *pb.Message) error {
	ms, err := dht.messageSenderForPeer(p)
	if err != nil {
		return err
	}

	if err := ms.SendMessage(ctx, pmes); err != nil {
		return err
	}
	logger.Event(ctx, "dhtSentMessage", dht.self, p, pmes)
	return nil
}

func (dht *IpfsDHT) updateFromMessage(ctx context.Context, p peer.ID, mes *pb.Message) error {
	// Make sure that this node is actually a DHT server, not just a client.
	protos, err := dht.peerstore.SupportsProtocols(p, dht.protocolStrs()...)
	if err == nil && len(protos) > 0 {
		dht.Update(ctx, p)
	}
	return nil
}

func (dht *IpfsDHT) messageSenderForPeer(p peer.ID) (*messageSender, error) {
	dht.smlk.Lock()
	ms, ok := dht.strmap[p]
	if ok {
		dht.smlk.Unlock()
		return ms, nil
	}
	ms = &messageSender{p: p, dht: dht}
	dht.strmap[p] = ms
	dht.smlk.Unlock()

	if err := ms.prepOrInvalidate(); err != nil {
		dht.smlk.Lock()
		defer dht.smlk.Unlock()

		if msCur, ok := dht.strmap[p]; ok {
			// Changed. Use the new one, old one is invalid and
			// not in the map so we can just throw it away.
			if ms != msCur {
				return msCur, nil
			}
			// Not changed, remove the now invalid stream from the
			// map.
			delete(dht.strmap, p)
		}
		// Invalid but not in map. Must have been removed by a disconnect.
		return nil, err
	}
	// All ready to go.
	return ms, nil
}

type messageSender struct {
	s   inet.Stream
	r   ggio.ReadCloser
	w   bufferedWriteCloser
	lk  sync.Mutex
	p   peer.ID
	dht *IpfsDHT

	invalid   bool
	singleMes int
}

// invalidate is called before this messageSender is removed from the strmap.
// It prevents the messageSender from being reused/reinitialized and then
// forgotten (leaving the stream open).
func (ms *messageSender) invalidate() {
	ms.invalid = true
	if ms.s != nil {
		ms.s.Reset()
		ms.s = nil
	}
}

func (ms *messageSender) prepOrInvalidate() error {
	ms.lk.Lock()
	defer ms.lk.Unlock()
	if err := ms.prep(); err != nil {
		ms.invalidate()
		return err
	}
	return nil
}

func (ms *messageSender) prep() error {
	if ms.invalid {
		return fmt.Errorf("message sender has been invalidated")
	}
	if ms.s != nil {
		return nil
	}

	nstr, err := ms.dht.host.NewStream(ms.dht.ctx, ms.p, ms.dht.protocols...)
	if err != nil {
		return err
	}

	ms.r = ggio.NewDelimitedReader(nstr, inet.MessageSizeMax)
	ms.w = newBufferedDelimitedWriter(nstr)
	ms.s = nstr

	return nil
}

// streamReuseTries is the number of times we will try to reuse a stream to a
// given peer before giving up and reverting to the old one-message-per-stream
// behaviour.
const streamReuseTries = 3

func (ms *messageSender) SendMessage(ctx context.Context, pmes *pb.Message) error {
	ms.lk.Lock()
	defer ms.lk.Unlock()
	retry := false
	for {
		if err := ms.prep(); err != nil {
			return err
		}

		if err := ms.writeMsg(pmes); err != nil {
			ms.s.Reset()
			ms.s = nil

			if retry {
				logger.Info("error writing message, bailing: ", err)
				return err
			} else {
				logger.Info("error writing message, trying again: ", err)
				retry = true
				continue
			}
		}

		logger.Event(ctx, "dhtSentMessage", ms.dht.self, ms.p, pmes)

		if ms.singleMes > streamReuseTries {
			go inet.FullClose(ms.s)
			ms.s = nil
		} else if retry {
			ms.singleMes++
		}

		return nil
	}
}

func (ms *messageSender) SendRequest(ctx context.Context, pmes *pb.Message) (*pb.Message, error) {
	ms.lk.Lock()
	defer ms.lk.Unlock()
	retry := false
	for {
		if err := ms.prep(); err != nil {
			return nil, err
		}

		if err := ms.writeMsg(pmes); err != nil {
			ms.s.Reset()
			ms.s = nil

			if retry {
				logger.Info("error writing message, bailing: ", err)
				return nil, err
			} else {
				logger.Info("error writing message, trying again: ", err)
				retry = true
				continue
			}
		}

		mes := new(pb.Message)
		if err := ms.ctxReadMsg(ctx, mes); err != nil {
			ms.s.Reset()
			ms.s = nil

			if retry {
				logger.Info("error reading message, bailing: ", err)
				return nil, err
			} else {
				logger.Info("error reading message, trying again: ", err)
				retry = true
				continue
			}
		}

		logger.Event(ctx, "dhtSentMessage", ms.dht.self, ms.p, pmes)

		if ms.singleMes > streamReuseTries {
			go inet.FullClose(ms.s)
			ms.s = nil
		} else if retry {
			ms.singleMes++
		}

		return mes, nil
	}
}

func (ms *messageSender) writeMsg(pmes *pb.Message) error {
	if err := ms.w.WriteMsg(pmes); err != nil {
		return err
	}
	return ms.w.Flush()
}

func (ms *messageSender) ctxReadMsg(ctx context.Context, mes *pb.Message) error {
	errc := make(chan error, 1)
	go func(r ggio.ReadCloser) {
		errc <- r.ReadMsg(mes)
	}(ms.r)

	t := time.NewTimer(dhtReadMessageTimeout)
	defer t.Stop()

	select {
	case err := <-errc:
		return err
	case <-ctx.Done():
		return ctx.Err()
	case <-t.C:
		return ErrReadTimeout
	}
}
<commit_msg>Rework handleNewStream (#267)<commit_after>package dht

import (
	"bufio"
	"context"
	"fmt"
	"io"
	"sync"
	"time"

	ggio "github.com/gogo/protobuf/io"
	ctxio "github.com/jbenet/go-context/io"
	pb "github.com/libp2p/go-libp2p-kad-dht/pb"
	inet "github.com/libp2p/go-libp2p-net"
	peer "github.com/libp2p/go-libp2p-peer"
)

var dhtReadMessageTimeout = time.Minute
var ErrReadTimeout = fmt.Errorf("timed out reading response")

type bufferedWriteCloser interface {
	ggio.WriteCloser
	Flush() error
}

// The Protobuf writer performs multiple small writes when writing a message.
// We need to buffer those writes, to make sure that we're not sending a new
// packet for every single write.
type bufferedDelimitedWriter struct {
	*bufio.Writer
	ggio.WriteCloser
}

func newBufferedDelimitedWriter(str io.Writer) bufferedWriteCloser {
	w := bufio.NewWriter(str)
	return &bufferedDelimitedWriter{
		Writer:      w,
		WriteCloser: ggio.NewDelimitedWriter(w),
	}
}

func (w *bufferedDelimitedWriter) Flush() error {
	return w.Writer.Flush()
}

// handleNewStream implements the inet.StreamHandler
func (dht *IpfsDHT) handleNewStream(s inet.Stream) {
	defer s.Reset()
	if dht.handleNewMessage(s) {
		// Gracefully close the stream for writes.
		s.Close()
	}
}

// Returns true on orderly completion of writes (so we can Close the stream).
func (dht *IpfsDHT) handleNewMessage(s inet.Stream) bool {
	ctx := dht.Context()
	cr := ctxio.NewReader(ctx, s) // ok to use. we defer close stream in this func
	cw := ctxio.NewWriter(ctx, s) // ok to use. we defer close stream in this func
	r := ggio.NewDelimitedReader(cr, inet.MessageSizeMax)
	w := newBufferedDelimitedWriter(cw)
	mPeer := s.Conn().RemotePeer()

	for {
		var req pb.Message
		switch err := r.ReadMsg(&req); err {
		case io.EOF:
			return true
		default:
			// This string test is necessary because there isn't a single stream reset error
			// instance	in use.
			if err.Error() != "stream reset" {
				logger.Debugf("error reading message: %#v", err)
			}
			return false
		case nil:
		}

		handler := dht.handlerForMsgType(req.GetType())
		if handler == nil {
			logger.Warningf("can't handle received message of type %v", req.GetType())
			return false
		}

		resp, err := handler(ctx, mPeer, &req)
		if err != nil {
			logger.Debugf("error handling message: %v", err)
			return false
		}

		dht.updateFromMessage(ctx, mPeer, &req)

		if resp == nil {
			continue
		}

		// send out response msg
		err = w.WriteMsg(resp)
		if err == nil {
			err = w.Flush()
		}
		if err != nil {
			logger.Debugf("error writing response: %v", err)
			return false
		}

	}
}

// sendRequest sends out a request, but also makes sure to
// measure the RTT for latency measurements.
func (dht *IpfsDHT) sendRequest(ctx context.Context, p peer.ID, pmes *pb.Message) (*pb.Message, error) {

	ms, err := dht.messageSenderForPeer(p)
	if err != nil {
		return nil, err
	}

	start := time.Now()

	rpmes, err := ms.SendRequest(ctx, pmes)
	if err != nil {
		return nil, err
	}

	// update the peer (on valid msgs only)
	dht.updateFromMessage(ctx, p, rpmes)

	dht.peerstore.RecordLatency(p, time.Since(start))
	logger.Event(ctx, "dhtReceivedMessage", dht.self, p, rpmes)
	return rpmes, nil
}

// sendMessage sends out a message
func (dht *IpfsDHT) sendMessage(ctx context.Context, p peer.ID, pmes *pb.Message) error {
	ms, err := dht.messageSenderForPeer(p)
	if err != nil {
		return err
	}

	if err := ms.SendMessage(ctx, pmes); err != nil {
		return err
	}
	logger.Event(ctx, "dhtSentMessage", dht.self, p, pmes)
	return nil
}

func (dht *IpfsDHT) updateFromMessage(ctx context.Context, p peer.ID, mes *pb.Message) error {
	// Make sure that this node is actually a DHT server, not just a client.
	protos, err := dht.peerstore.SupportsProtocols(p, dht.protocolStrs()...)
	if err == nil && len(protos) > 0 {
		dht.Update(ctx, p)
	}
	return nil
}

func (dht *IpfsDHT) messageSenderForPeer(p peer.ID) (*messageSender, error) {
	dht.smlk.Lock()
	ms, ok := dht.strmap[p]
	if ok {
		dht.smlk.Unlock()
		return ms, nil
	}
	ms = &messageSender{p: p, dht: dht}
	dht.strmap[p] = ms
	dht.smlk.Unlock()

	if err := ms.prepOrInvalidate(); err != nil {
		dht.smlk.Lock()
		defer dht.smlk.Unlock()

		if msCur, ok := dht.strmap[p]; ok {
			// Changed. Use the new one, old one is invalid and
			// not in the map so we can just throw it away.
			if ms != msCur {
				return msCur, nil
			}
			// Not changed, remove the now invalid stream from the
			// map.
			delete(dht.strmap, p)
		}
		// Invalid but not in map. Must have been removed by a disconnect.
		return nil, err
	}
	// All ready to go.
	return ms, nil
}

type messageSender struct {
	s   inet.Stream
	r   ggio.ReadCloser
	w   bufferedWriteCloser
	lk  sync.Mutex
	p   peer.ID
	dht *IpfsDHT

	invalid   bool
	singleMes int
}

// invalidate is called before this messageSender is removed from the strmap.
// It prevents the messageSender from being reused/reinitialized and then
// forgotten (leaving the stream open).
func (ms *messageSender) invalidate() {
	ms.invalid = true
	if ms.s != nil {
		ms.s.Reset()
		ms.s = nil
	}
}

func (ms *messageSender) prepOrInvalidate() error {
	ms.lk.Lock()
	defer ms.lk.Unlock()
	if err := ms.prep(); err != nil {
		ms.invalidate()
		return err
	}
	return nil
}

func (ms *messageSender) prep() error {
	if ms.invalid {
		return fmt.Errorf("message sender has been invalidated")
	}
	if ms.s != nil {
		return nil
	}

	nstr, err := ms.dht.host.NewStream(ms.dht.ctx, ms.p, ms.dht.protocols...)
	if err != nil {
		return err
	}

	ms.r = ggio.NewDelimitedReader(nstr, inet.MessageSizeMax)
	ms.w = newBufferedDelimitedWriter(nstr)
	ms.s = nstr

	return nil
}

// streamReuseTries is the number of times we will try to reuse a stream to a
// given peer before giving up and reverting to the old one-message-per-stream
// behaviour.
const streamReuseTries = 3

func (ms *messageSender) SendMessage(ctx context.Context, pmes *pb.Message) error {
	ms.lk.Lock()
	defer ms.lk.Unlock()
	retry := false
	for {
		if err := ms.prep(); err != nil {
			return err
		}

		if err := ms.writeMsg(pmes); err != nil {
			ms.s.Reset()
			ms.s = nil

			if retry {
				logger.Info("error writing message, bailing: ", err)
				return err
			} else {
				logger.Info("error writing message, trying again: ", err)
				retry = true
				continue
			}
		}

		logger.Event(ctx, "dhtSentMessage", ms.dht.self, ms.p, pmes)

		if ms.singleMes > streamReuseTries {
			go inet.FullClose(ms.s)
			ms.s = nil
		} else if retry {
			ms.singleMes++
		}

		return nil
	}
}

func (ms *messageSender) SendRequest(ctx context.Context, pmes *pb.Message) (*pb.Message, error) {
	ms.lk.Lock()
	defer ms.lk.Unlock()
	retry := false
	for {
		if err := ms.prep(); err != nil {
			return nil, err
		}

		if err := ms.writeMsg(pmes); err != nil {
			ms.s.Reset()
			ms.s = nil

			if retry {
				logger.Info("error writing message, bailing: ", err)
				return nil, err
			} else {
				logger.Info("error writing message, trying again: ", err)
				retry = true
				continue
			}
		}

		mes := new(pb.Message)
		if err := ms.ctxReadMsg(ctx, mes); err != nil {
			ms.s.Reset()
			ms.s = nil

			if retry {
				logger.Info("error reading message, bailing: ", err)
				return nil, err
			} else {
				logger.Info("error reading message, trying again: ", err)
				retry = true
				continue
			}
		}

		logger.Event(ctx, "dhtSentMessage", ms.dht.self, ms.p, pmes)

		if ms.singleMes > streamReuseTries {
			go inet.FullClose(ms.s)
			ms.s = nil
		} else if retry {
			ms.singleMes++
		}

		return mes, nil
	}
}

func (ms *messageSender) writeMsg(pmes *pb.Message) error {
	if err := ms.w.WriteMsg(pmes); err != nil {
		return err
	}
	return ms.w.Flush()
}

func (ms *messageSender) ctxReadMsg(ctx context.Context, mes *pb.Message) error {
	errc := make(chan error, 1)
	go func(r ggio.ReadCloser) {
		errc <- r.ReadMsg(mes)
	}(ms.r)

	t := time.NewTimer(dhtReadMessageTimeout)
	defer t.Stop()

	select {
	case err := <-errc:
		return err
	case <-ctx.Done():
		return ctx.Err()
	case <-t.C:
		return ErrReadTimeout
	}
}
<|endoftext|>