<commit_before>/*
 * Mini Copy, (C) 2015 Minio, Inc.
 *
 * 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 main

import (
	"io"

	"github.com/cheggaaa/pb"
	"github.com/minio-io/cli"
	"github.com/minio-io/mc/pkg/client"
	"github.com/minio-io/mc/pkg/console"
	"github.com/minio-io/minio/pkg/iodine"
	"github.com/minio-io/minio/pkg/utils/log"
)

func getSourceObjectList(sourceClnt client.Client, sourceURLParser *parsedURL) ([]*client.Item, error) {
	sourceObjects, err := sourceClnt.ListObjects(sourceURLParser.bucketName, sourceURLParser.objectName)
	if err != nil {
		return nil, iodine.New(err, nil)
	}
	return sourceObjects, nil
}

func getRecursiveTargetWriter(targetURLParser *parsedURL, targetObject, md5Hex string, length int64) (io.WriteCloser, error) {
	targetClnt, err := getNewClient(targetURLParser, globalDebugFlag)
	if err != nil {
		return nil, iodine.New(err, map[string]string{"failedURL": targetURLParser.String()})
	}
	targetBucket := targetURLParser.bucketName
	// check if bucket is valid, if not found create it
	if err := targetClnt.StatBucket(targetBucket); err != nil {
		switch iodine.ToError(err).(type) {
		case client.BucketNotFound:
			err := targetClnt.PutBucket(targetBucket)
			if err != nil {
				return nil, iodine.New(err, map[string]string{"failedURL": targetURLParser.String()})
			}
		default:
			return nil, iodine.New(err, map[string]string{"failedURL": targetURLParser.String()})
		}
	}
	return targetClnt.Put(targetBucket, targetObject, md5Hex, length)
}

// doCopyCmdRecursive - copy bucket to bucket
func doCopyCmdRecursive(ctx *cli.Context) {
	// Convert arguments to URLs: expand alias, fix format...
	urlParsers, err := parseURLs(ctx)
	if err != nil {
		log.Debug.Println(iodine.New(err, nil))
		console.Fatalf("mc: unable to parse urls: %s\n", err)
	}
	sourceURLParser := urlParsers[0] // First arg is source
	targetURLParser := urlParsers[1] // 1 target for now - TODO(y4m4): 2 or more targets

	sourceClnt, err := getNewClient(sourceURLParser, globalDebugFlag)
	if err != nil {
		log.Debug.Println(iodine.New(err, nil))
		console.Fatalf("mc: unable to get source: %s\n", err)
	}
	sourceObjectList, err := getSourceObjectList(sourceClnt, sourceURLParser)
	if err != nil {
		log.Debug.Println(iodine.New(err, nil))
		console.Fatalf("mc: unable to list source objects: %s\n", err)
	}
	// do not exit, continue even for failures
	for _, sourceObject := range sourceObjectList {
		reader, length, md5hex, err := sourceClnt.Get(sourceURLParser.bucketName, sourceObject.Key)
		if err != nil {
			log.Debug.Println(iodine.New(err, nil))
			console.Errorf("mc: unable to read source: %s\n", err)
		}
		writeCloser, err := getRecursiveTargetWriter(targetURLParser, sourceObject.Key, md5hex, length)
		if err != nil {
			log.Debug.Println(iodine.New(err, nil))
			console.Errorf("mc: unable to read target: %s\n", err)
		}
		var writers []io.Writer
		writers = append(writers, writeCloser)

		// set up progress bar
		var bar *pb.ProgressBar
		if !globalQuietFlag {
			bar = startBar(length)
			bar.Start()
			writers = append(writers, bar)
		}

		// write progress bar
		multiWriter := io.MultiWriter(writers...)

		// copy data to writers
		_, err = io.CopyN(multiWriter, reader, length)
		if err != nil {
			log.Debug.Println(iodine.New(err, nil))
			console.Errorln("mc: Unable to write to target")
		}

		// close writers
		err = writeCloser.Close()
		if err != nil {
			log.Debug.Println(iodine.New(err, nil))
			console.Errorln("mc: Unable to close writer, object may not of written properly.")
		}
	}
	return
}
<commit_msg>Implement recursive upload to many targets<commit_after>/*
 * Mini Copy, (C) 2015 Minio, Inc.
 *
 * 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 main

import (
	"io"

	"github.com/cheggaaa/pb"
	"github.com/minio-io/cli"
	"github.com/minio-io/mc/pkg/client"
	"github.com/minio-io/mc/pkg/console"
	"github.com/minio-io/minio/pkg/iodine"
	"github.com/minio-io/minio/pkg/utils/log"
)

func getSourceObjectList(sourceClnt client.Client, sourceURLParser *parsedURL) ([]*client.Item, error) {
	sourceObjects, err := sourceClnt.ListObjects(sourceURLParser.bucketName, sourceURLParser.objectName)
	if err != nil {
		return nil, iodine.New(err, nil)
	}
	return sourceObjects, nil
}

func getRecursiveTargetWriter(targetURLParser *parsedURL, targetObject, md5Hex string, length int64) (io.WriteCloser, error) {
	targetClnt, err := getNewClient(targetURLParser, globalDebugFlag)
	if err != nil {
		return nil, iodine.New(err, map[string]string{"failedURL": targetURLParser.String()})
	}
	targetBucket := targetURLParser.bucketName
	// check if bucket is valid, if not found create it
	if err := targetClnt.StatBucket(targetBucket); err != nil {
		switch iodine.ToError(err).(type) {
		case client.BucketNotFound:
			err := targetClnt.PutBucket(targetBucket)
			if err != nil {
				return nil, iodine.New(err, map[string]string{"failedURL": targetURLParser.String()})
			}
		default:
			return nil, iodine.New(err, map[string]string{"failedURL": targetURLParser.String()})
		}
	}
	return targetClnt.Put(targetBucket, targetObject, md5Hex, length)
}

func getRecursiveTargetWriters(targetURLParsers []*parsedURL, targetObject, md5Hex string, length int64) ([]io.WriteCloser, error) {
	var targetWriters []io.WriteCloser
	for _, targetURLParser := range targetURLParsers {
		writer, err := getRecursiveTargetWriter(targetURLParser, targetObject, md5Hex, length)
		if err != nil {
			// close all writers
			for _, targetWriter := range targetWriters {
				targetWriter.Close()
			}
			return nil, iodine.New(err, map[string]string{"failedURL": targetURLParser.String()})
		}
		targetWriters = append(targetWriters, writer)
	}
	return targetWriters, nil
}

// doCopyCmdRecursive - copy bucket to bucket
func doCopyCmdRecursive(ctx *cli.Context) {
	// Convert arguments to URLs: expand alias, fix format...
	urlParsers, err := parseURLs(ctx)
	if err != nil {
		log.Debug.Println(iodine.New(err, nil))
		console.Fatalf("mc: unable to parse urls: %s\n", err)
	}
	sourceURLParser := urlParsers[0]   // First arg is source
	targetURLsParser := urlParsers[1:] // Rest are targets

	sourceClnt, err := getNewClient(sourceURLParser, globalDebugFlag)
	if err != nil {
		log.Debug.Println(iodine.New(err, nil))
		console.Fatalf("mc: unable to get source: %s\n", err)
	}
	sourceObjectList, err := getSourceObjectList(sourceClnt, sourceURLParser)
	if err != nil {
		log.Debug.Println(iodine.New(err, nil))
		console.Fatalf("mc: unable to list source objects: %s\n", err)
	}
	// do not exit, continue even for failures
	for _, sourceObject := range sourceObjectList {
		reader, length, md5hex, err := sourceClnt.Get(sourceURLParser.bucketName, sourceObject.Key)
		if err != nil {
			log.Debug.Println(iodine.New(err, nil))
			console.Errorf("mc: unable to read source: %s\n", err)
		}
		writeClosers, err := getRecursiveTargetWriters(targetURLsParser, sourceObject.Key, md5hex, length)
		if err != nil {
			log.Debug.Println(iodine.New(err, nil))
			console.Errorf("mc: unable to read target: %s\n", err)
		}

		var writers []io.Writer
		for _, writer := range writeClosers {
			writers = append(writers, writer)
		}

		// set up progress bar
		var bar *pb.ProgressBar
		if !globalQuietFlag {
			bar = startBar(length)
			bar.Start()
			writers = append(writers, bar)
		}

		// write progress bar
		multiWriter := io.MultiWriter(writers...)

		// copy data to writers
		_, err = io.CopyN(multiWriter, reader, length)
		if err != nil {
			log.Debug.Println(iodine.New(err, nil))
			console.Errorln("mc: Unable to write to target")
		}

		// close writers
		for _, writer := range writeClosers {
			err := writer.Close()
			if err != nil {
				log.Debug.Println(iodine.New(err, nil))
				console.Errorln("mc: Unable to close writer, object may not of written properly.")
			}
		}
	}
	return
}
<|endoftext|>


<commit_before>package client

import (
	"fmt"

	"github.com/TeaMeow/KitSvc/model"
)

type Client interface {
	PostUser(*model.User) (*model.User, []error)
	GetUser(string) (*model.User, []error)
	PutUser(int, *model.User) (*model.User, []error)
	DeleteUser(int, *model.User) (*model.User, []error)
	PostAuth(*model.User) (string, []error)
}

func uri(path string, params ...interface{}) string {
	return fmt.Sprintf(path, params...)
}
<commit_msg>client converts int to string in path<commit_after>package client

import (
	"fmt"
	"strconv"

	"github.com/TeaMeow/KitSvc/model"
)

type Client interface {
	PostUser(*model.User) (*model.User, []error)
	GetUser(string) (*model.User, []error)
	PutUser(int, *model.User) (*model.User, []error)
	DeleteUser(int, *model.User) (*model.User, []error)
	PostAuth(*model.User) (*model.Token, []error)
}

func uri(path string, params ...interface{}) string {
	for i, v := range params {
		switch v.(type) {
		case int:
			params[i] = strconv.Itoa(v.(int))
		}
	}

	return fmt.Sprintf(path, params...)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"

	"github.com/docopt/docopt-go"
	"github.com/fatih/color"
	"github.com/gorilla/handlers"
	"github.com/hako/afto/afutil"
)

var (
	port     = "2468"
	version  = "0.2"
	repoPath = ""
	file     = ""

	aftoUnexpectedError = "An unexpected error occured while updating. Make sure the path to the .deb file is correct."
)

var header = `
<!DOCTYPE html>

		<meta charset="UTF-8">
		<title>afto</title>

afto

`
var footer = `

generated by afto - the cydia repo generator/manager.

` + version + ` | github

`

var usage = `afto ` + version + `

Usage:
  afto new <name>
  afto update -r <name> [-f <file> | --file <file>]
  afto [-d <dir> | --dir <dir>] [-p <port> | --port <port>]
  afto [-c <file> | --control <file>]

options:
  -c, --control  Specify control file to use.
  -p, --port     Specify port number for afto.
  -h, --help     Show this screen.
  --version      Show version.

commands:
  new             Generate a new cydia repo.`

// AftoRepo represents a cydia repo with a name.
type AftoRepo struct {
	Name      string
	Debs      []string
	Cmd       string
	SingleDeb string
}

func main() {
	// Parse flags.
	if len(os.Args) == 1 {
		fmt.Println(usage)
		os.Exit(1)
	}

	// Parse options with docopt.
	opts, _ := docopt.Parse(usage, nil, true, "afto "+version, false)
	log.SetPrefix("afto: ")
	log.SetFlags(2)

	if (opts["-d"] != true && opts["--dir"] != true) && opts["new"] != true && opts["update"] != true {
		fmt.Println("afto: -d or --dir is required")
		os.Exit(1)
	}

	if opts["-p"] == true || opts["--port"] == true {
		argport := opts["<port>"].(string)
		port = argport
	}

	if opts["-f"] == true || opts["--file"] == true {
		optsfile := opts["<file>"].(string)
		file = optsfile
	}

	// New command function.
	if opts["new"] == true {
		name := opts["<name>"].(string)
		af := &AftoRepo{Name: name, Cmd: "new"}
		af.newRepo()
		os.Exit(0)
	}

	// Afto update command
	if opts["update"] == true {
		var af *AftoRepo
		name := opts["<name>"].(string)

		if file != "" {
			af = &AftoRepo{Name: name, Cmd: "update", SingleDeb: file}
		} else {
			af = &AftoRepo{Name: name, Cmd: "update"}
		}
		af.updateRepo()
		os.Exit(0)
	}

	var dir = opts["<dir>"].(string)

	finalPath, gtderr := afutil.GetRepo(dir)
	if gtderr != nil {
		log.Fatalln("afto: " + gtderr.Error())
		os.Exit(1)
	}

	repoPath = finalPath

	// afto watches, listens and takes action. (afto listens on 0.0.0.0:[port])
	color.Cyan("afto (αυτο) v" + version + " - the cydia repo generator/manager.")
	color.Cyan("(c) 2016 Wesley Hill (@hako/@hakobyte)")
	fmt.Println("afto is watching & listening for connections on port " + port)

	// Add middleware.
	mx := http.FileServer(http.Dir(repoPath))
	loggingHandler := handlers.LoggingHandler(os.Stdout, mx)

	// AFTODO: Put watcher command here.

	// Spin up a goroutine and serve the repo.
	go func() {
		err := http.ListenAndServe(":"+port, loggingHandler)
		if err != nil {
			fmt.Println("afto: error " + err.Error())
			os.Exit(1)
		}
	}()

	select {}
}

// walkRepo checks multiple directories to see if they have the required files of
// a cydia repo. (running afto on its own triggers this.)
func walkRepos() {

}

// checkReqs checks for dpkg and deb files.
func (af *AftoRepo) checkReqs() {
	// Check for the dpkg command.
	err := afutil.CheckDPKG()
	if err != nil {
		log.Println(err)
		// Now check for compatible platform.
		message, err := afutil.DetectPlatform()
		if err != nil {
			log.Fatalln(err)
		}
		fmt.Println(message)
	}
	// Check for deb files. De(b)pending on the command given.
	log.Println("checking for deb files...")
	if af.Cmd == "new" {
		debs, err := afutil.CheckDeb()
		if err != nil {
			log.Fatalln(err)
		}
		log.Println(strconv.Itoa(len(debs)) + " deb file(s) found.")
		af.Debs = debs
	}
	if af.Cmd == "update" {
		// We must compare debfile with the repo debfile. (Needs refactoring.)
		if file != "" {

			deb, err := afutil.CheckDebWithFile(af.SingleDeb)
			if err != nil {
				log.Fatalln(err)
			}
			log.Println("deb file: \"" + filepath.Base(af.SingleDeb) + "\" found.")
			af.Debs = append(af.Debs, deb)
		}
		debs, err := afutil.CheckDebWithPath(af.Name)
		if err != nil {
			log.Fatalln(err)
		}
		log.Println(strconv.Itoa(len(debs)) + " deb file(s) found.")
		for _, d := range debs {
			af.Debs = append(af.Debs, af.Name+"/"+d)
		}
	}
}

// newRepo generates a new cydia compatible repo.
func (af *AftoRepo) newRepo() {
	var body string
	af.checkReqs()
	log.Println("generating repo: \"" + af.Name + "\"")
	os.Mkdir(af.Name, 0755)
	// Execute dpkg script.
	direrr := af.executeDpkgScript()
	if direrr != nil {
		log.Fatalln(direrr)
	}
	log.Println("generated Packages file.")
	// Execute bzip command.
	bzerr := afutil.BzipPackages()
	if bzerr != nil {
		log.Fatalln(bzerr)
	}
	log.Println("bzipped Packages file.")
	// Create Release file.
	rfile, rfilerr := afutil.ReleaseFile("afto beta repo", "apt.afto.repo", "A default repo generated by afto", "afto", "beta")
	if rfilerr != nil {
		log.Fatalln(rfilerr)
	}
	rf, rferr := os.Create(af.Name + "/Release")
	if rferr != nil {
		log.Fatalln(rferr)
	}
	rf.WriteString(rfile)
	log.Println("created Release file.")

	htmlFile, hterr := os.Create("index.html")
	if hterr != nil {
		log.Println(hterr)
	}

	// Restore the icons too.
	cyiconerr := RestoreAsset(".", "CydiaIcon.png")
	if cyiconerr != nil {
		log.Fatalln(cyiconerr)
	}
	cyicon2err := RestoreAsset(".", "CydiaIcon@2x.png")
	if cyicon2err != nil {
		log.Fatalln(cyicon2err)
	}
	cyicon3err := RestoreAsset(".", "CydiaIcon@3x.png")
	if cyicon3err != nil {
		log.Fatalln(cyicon3err)
	}

	// Move files to repo & generate HTML file.
	os.Rename("Packages", af.Name+"/Packages")
	os.Rename("Packages.bz2", af.Name+"/Packages.bz2")
	os.Rename("CydiaIcon.png", af.Name+"/CydiaIcon.png")
	os.Rename("CydiaIcon@2x.png", af.Name+"/CydiaIcon@2x.png")
	os.Rename("CydiaIcon@3x.png", af.Name+"/CydiaIcon@3x.png")
	for _, deb := range af.Debs {
		os.Rename(deb, af.Name+"/"+deb)
		body += fmt.Sprintln(`<pre><a href="` + deb + `">` + deb + `</a></pre>`)
	}
	htmlFile.WriteString(header + body + footer)
	os.Rename("index.html", af.Name+"/index.html")
}

// updateRepo updates all the packages that exist in the current repo.
func (af *AftoRepo) updateRepo() {
	af.checkReqs()
	log.Println("updating repo: \"" + af.Name + "\"")
	path, err := afutil.GetRepo(af.Name)
	if err != nil {
		log.Fatalln(err)
	}

	// The debfile we are comparing to in -r <deb>
	inputDeb, err := afutil.ParseDeb(file)
	if err != nil {
		log.Fatalf(aftoUnexpectedError)
	}
	var newDeb, name, version, oldDeb = false, "", "", ""

	// The debfile we are comparing against <repo>
	for _, deb := range af.Debs {
		repodeb, err := afutil.ParseDeb(deb)
		if err != nil {
			log.Fatalf(aftoUnexpectedError)
		}
		if repodeb.Name() == inputDeb.Name() && repodeb.Version() != inputDeb.Version() {
			newDeb = true
			name = inputDeb.Name()
			version = inputDeb.Version()
			oldDeb = deb
		}
	}
	if newDeb != true {
		log.Println("No update is available.")
		os.Exit(0)
	}

	// Copy updated deb outside of folder.
	log.Println("Update is available for \"" + name + "\" version " + version)
	stripedRepo := strings.Replace(path, af.Name, "", -1)
	e := afutil.Copy(af.SingleDeb, stripedRepo+filepath.Base(af.SingleDeb))
	if err != nil {
		fmt.Println(e)
	}

	// Move other debs outside of folder, except old one. Delete that.
	currentDebs, _ := afutil.CheckDebWithPath(path)
	for _, c := range currentDebs {
		if filepath.Base(oldDeb) == c {
			continue
		}
		movedDeb := stripedRepo + filepath.Base(c)
		os.Rename(path+"/"+c, movedDeb)
	}

	// Delete and recreate repo. change command to update.
	os.RemoveAll(path)
	af.Cmd = "new"
	af.newRepo()
}

// runScript executes the dpkg scan packages command.
func (af *AftoRepo) runScript() ([]byte, error) {
	cwd, err := os.Getwd()
	if err != nil {
		return nil, err
	}
	// Restore required assets to current directory.
	path, _ := filepath.Abs(cwd)
	dataerr := RestoreAsset(path, ".dpkg-scanpackages")
	if dataerr != nil {
		return nil, dataerr
	}
	txterr := RestoreAsset(path, ".dpkg-gettext.pl")
	if txterr != nil {
		return nil, txterr
	}
	// Run ./dpkg-scanpackages -m . /dev/null > Packages and save the output.
	packages, cmderr := exec.Command("./.dpkg-scanpackages", "-m", ".", "/dev/null").Output()
	if cmderr != nil {
		return nil, cmderr
	}
	return packages, nil
}

// executeDpkgScript executes a commandline script which creates a 'Packages' file.
func (af *AftoRepo) executeDpkgScript() error {

	output, screrr := af.runScript()

	if screrr != nil {
		return screrr
	}

	file, err := os.Create("Packages")
	if err != nil {
		return err
	}
	// Write the Packages file.
	_, werr := file.Write(output)
	if werr != nil {
		return werr
	}
	defer file.Close()
	// Remove unwanted assets.
	for _, asset := range AssetNames() {
		os.Remove(asset)
	}
	return nil
}
<commit_msg>Added sign option in usage<commit_after>package main

import (
	"fmt"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"

	"github.com/docopt/docopt-go"
	"github.com/fatih/color"
	"github.com/gorilla/handlers"
	"github.com/hako/afto/afutil"
)

var (
	port     = "2468"
	version  = "0.2"
	repoPath = ""
	file     = ""

	aftoUnexpectedError = "An unexpected error occured while updating. Make sure the path to the .deb file is correct."
)

var header = `
<!DOCTYPE html>

		<meta charset="UTF-8">
		<title>afto</title>

afto

`
var footer = `

generated by afto - the cydia repo generator/manager.

` + version + ` | github

`

var usage = `afto ` + version + `

Usage:
  afto new <name>
  afto update -r <name> [-f <file> | --file <file>]
  afto [-d <dir> | --dir <dir>] [-p <port> | --port <port>]
  afto [-c <file> | --control <file>]
  afto [-s <dir> | --sign <dir>]

options:
  -c, --control  Specify control file to use.
  -p, --port     Specify port number for afto.
  -h, --help     Show this screen.
  --version      Show version.

commands:
  new             Generate a new cydia repo.`

// AftoRepo represents a cydia repo with a name.
type AftoRepo struct {
	Name      string
	Debs      []string
	Cmd       string
	SingleDeb string
}

func main() {
	// Parse flags.
	if len(os.Args) == 1 {
		fmt.Println(usage)
		os.Exit(1)
	}

	// Parse options with docopt.
	opts, _ := docopt.Parse(usage, nil, true, "afto "+version, false)
	log.SetPrefix("afto: ")
	log.SetFlags(2)

	if (opts["-d"] != true && opts["--dir"] != true) && opts["new"] != true && opts["update"] != true {
		fmt.Println("afto: -d or --dir is required")
		os.Exit(1)
	}

	if opts["-p"] == true || opts["--port"] == true {
		argport := opts["<port>"].(string)
		port = argport
	}

	if opts["-f"] == true || opts["--file"] == true {
		optsfile := opts["<file>"].(string)
		file = optsfile
	}

	// New command function.
	if opts["new"] == true {
		name := opts["<name>"].(string)
		af := &AftoRepo{Name: name, Cmd: "new"}
		af.newRepo()
		os.Exit(0)
	}

	// Afto update command
	if opts["update"] == true {
		var af *AftoRepo
		name := opts["<name>"].(string)

		if file != "" {
			af = &AftoRepo{Name: name, Cmd: "update", SingleDeb: file}
		} else {
			af = &AftoRepo{Name: name, Cmd: "update"}
		}
		af.updateRepo()
		os.Exit(0)
	}

	var dir = opts["<dir>"].(string)

	finalPath, gtderr := afutil.GetRepo(dir)
	if gtderr != nil {
		log.Fatalln("afto: " + gtderr.Error())
		os.Exit(1)
	}

	repoPath = finalPath

	// afto watches, listens and takes action. (afto listens on 0.0.0.0:[port])
	color.Cyan("afto (αυτο) v" + version + " - the cydia repo generator/manager.")
	color.Cyan("(c) 2016 Wesley Hill (@hako/@hakobyte)")
	fmt.Println("afto is watching & listening for connections on port " + port)

	// Add middleware.
	mx := http.FileServer(http.Dir(repoPath))
	loggingHandler := handlers.LoggingHandler(os.Stdout, mx)

	// AFTODO: Put watcher command here.

	// Spin up a goroutine and serve the repo.
	go func() {
		err := http.ListenAndServe(":"+port, loggingHandler)
		if err != nil {
			fmt.Println("afto: error " + err.Error())
			os.Exit(1)
		}
	}()

	select {}
}

// walkRepo checks multiple directories to see if they have the required files of
// a cydia repo. (running afto on its own triggers this.)
func walkRepos() {

}

// checkReqs checks for dpkg and deb files.
func (af *AftoRepo) checkReqs() {
	// Check for the dpkg command.
	err := afutil.CheckDPKG()
	if err != nil {
		log.Println(err)
		// Now check for compatible platform.
		message, err := afutil.DetectPlatform()
		if err != nil {
			log.Fatalln(err)
		}
		fmt.Println(message)
	}
	// Check for deb files. De(b)pending on the command given.
	log.Println("checking for deb files...")
	if af.Cmd == "new" {
		debs, err := afutil.CheckDeb()
		if err != nil {
			log.Fatalln(err)
		}
		log.Println(strconv.Itoa(len(debs)) + " deb file(s) found.")
		af.Debs = debs
	}
	if af.Cmd == "update" {
		// We must compare debfile with the repo debfile. (Needs refactoring.)
		if file != "" {

			deb, err := afutil.CheckDebWithFile(af.SingleDeb)
			if err != nil {
				log.Fatalln(err)
			}
			log.Println("deb file: \"" + filepath.Base(af.SingleDeb) + "\" found.")
			af.Debs = append(af.Debs, deb)
		}
		debs, err := afutil.CheckDebWithPath(af.Name)
		if err != nil {
			log.Fatalln(err)
		}
		log.Println(strconv.Itoa(len(debs)) + " deb file(s) found.")
		for _, d := range debs {
			af.Debs = append(af.Debs, af.Name+"/"+d)
		}
	}
}

// newRepo generates a new cydia compatible repo.
func (af *AftoRepo) newRepo() {
	var body string
	af.checkReqs()
	log.Println("generating repo: \"" + af.Name + "\"")
	os.Mkdir(af.Name, 0755)
	// Execute dpkg script.
	direrr := af.executeDpkgScript()
	if direrr != nil {
		log.Fatalln(direrr)
	}
	log.Println("generated Packages file.")
	// Execute bzip command.
	bzerr := afutil.BzipPackages()
	if bzerr != nil {
		log.Fatalln(bzerr)
	}
	log.Println("bzipped Packages file.")
	// Create Release file.
	rfile, rfilerr := afutil.ReleaseFile("afto beta repo", "apt.afto.repo", "A default repo generated by afto", "afto", "beta")
	if rfilerr != nil {
		log.Fatalln(rfilerr)
	}
	rf, rferr := os.Create(af.Name + "/Release")
	if rferr != nil {
		log.Fatalln(rferr)
	}
	rf.WriteString(rfile)
	log.Println("created Release file.")

	htmlFile, hterr := os.Create("index.html")
	if hterr != nil {
		log.Println(hterr)
	}

	// Restore the icons too.
	cyiconerr := RestoreAsset(".", "CydiaIcon.png")
	if cyiconerr != nil {
		log.Fatalln(cyiconerr)
	}
	cyicon2err := RestoreAsset(".", "CydiaIcon@2x.png")
	if cyicon2err != nil {
		log.Fatalln(cyicon2err)
	}
	cyicon3err := RestoreAsset(".", "CydiaIcon@3x.png")
	if cyicon3err != nil {
		log.Fatalln(cyicon3err)
	}

	// Move files to repo & generate HTML file.
	os.Rename("Packages", af.Name+"/Packages")
	os.Rename("Packages.bz2", af.Name+"/Packages.bz2")
	os.Rename("CydiaIcon.png", af.Name+"/CydiaIcon.png")
	os.Rename("CydiaIcon@2x.png", af.Name+"/CydiaIcon@2x.png")
	os.Rename("CydiaIcon@3x.png", af.Name+"/CydiaIcon@3x.png")
	for _, deb := range af.Debs {
		os.Rename(deb, af.Name+"/"+deb)
		body += fmt.Sprintln(`<pre><a href="` + deb + `">` + deb + `</a></pre>`)
	}
	htmlFile.WriteString(header + body + footer)
	os.Rename("index.html", af.Name+"/index.html")
}

// updateRepo updates all the packages that exist in the current repo.
func (af *AftoRepo) updateRepo() {
	af.checkReqs()
	log.Println("updating repo: \"" + af.Name + "\"")
	path, err := afutil.GetRepo(af.Name)
	if err != nil {
		log.Fatalln(err)
	}

	// The debfile we are comparing to in -r <deb>
	inputDeb, err := afutil.ParseDeb(file)
	if err != nil {
		log.Fatalf(aftoUnexpectedError)
	}
	var newDeb, name, version, oldDeb = false, "", "", ""

	// The debfile we are comparing against <repo>
	for _, deb := range af.Debs {
		repodeb, err := afutil.ParseDeb(deb)
		if err != nil {
			log.Fatalf(aftoUnexpectedError)
		}
		if repodeb.Name() == inputDeb.Name() && repodeb.Version() != inputDeb.Version() {
			newDeb = true
			name = inputDeb.Name()
			version = inputDeb.Version()
			oldDeb = deb
		}
	}
	if newDeb != true {
		log.Println("No update is available.")
		os.Exit(0)
	}

	// Copy updated deb outside of folder.
	log.Println("Update is available for \"" + name + "\" version " + version)
	stripedRepo := strings.Replace(path, af.Name, "", -1)
	e := afutil.Copy(af.SingleDeb, stripedRepo+filepath.Base(af.SingleDeb))
	if err != nil {
		fmt.Println(e)
	}

	// Move other debs outside of folder, except old one. Delete that.
	currentDebs, _ := afutil.CheckDebWithPath(path)
	for _, c := range currentDebs {
		if filepath.Base(oldDeb) == c {
			continue
		}
		movedDeb := stripedRepo + filepath.Base(c)
		os.Rename(path+"/"+c, movedDeb)
	}

	// Delete and recreate repo. change command to update.
	os.RemoveAll(path)
	af.Cmd = "new"
	af.newRepo()
}

// runScript executes the dpkg scan packages command.
func (af *AftoRepo) runScript() ([]byte, error) {
	cwd, err := os.Getwd()
	if err != nil {
		return nil, err
	}
	// Restore required assets to current directory.
	path, _ := filepath.Abs(cwd)
	dataerr := RestoreAsset(path, ".dpkg-scanpackages")
	if dataerr != nil {
		return nil, dataerr
	}
	txterr := RestoreAsset(path, ".dpkg-gettext.pl")
	if txterr != nil {
		return nil, txterr
	}
	// Run ./dpkg-scanpackages -m . /dev/null > Packages and save the output.
	packages, cmderr := exec.Command("./.dpkg-scanpackages", "-m", ".", "/dev/null").Output()
	if cmderr != nil {
		return nil, cmderr
	}
	return packages, nil
}

// executeDpkgScript executes a commandline script which creates a 'Packages' file.
func (af *AftoRepo) executeDpkgScript() error {

	output, screrr := af.runScript()

	if screrr != nil {
		return screrr
	}

	file, err := os.Create("Packages")
	if err != nil {
		return err
	}
	// Write the Packages file.
	_, werr := file.Write(output)
	if werr != nil {
		return werr
	}
	defer file.Close()
	// Remove unwanted assets.
	for _, asset := range AssetNames() {
		os.Remove(asset)
	}
	return nil
}
<|endoftext|>


<commit_before>// Copyright 2016 IBM Corporation
//
//   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 client

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"
	"time"
)

const defaultTimeout = time.Second * 30

// Client defines the interface used by clients of the Amalgam8 Service Registry.
type Client interface {
	Register(instance *ServiceInstance) (*ServiceInstance, error)
	Deregister(id string) error
	Renew(id string) error
	ListServices() ([]string, error)
	ListInstances(instanceFilter *InstanceFilter) ([]*ServiceInstance, error)
	ListServiceInstances(serviceName string) ([]*ServiceInstance, error)
}

// TODO: revamp URLs construction (resolve, URL-encode)

type ClientConfig struct {
	URL       string
	AuthToken string
	HTTPClient *http.Client
}

// RESTClient implements the Client interface using Amalgam8 Service Registry REST API.
type RESTClient struct {
	config     ClientConfig
	httpClient *http.Client
}

func NewRESTClient(config ClientConfig) (*RESTClient, error) {
	// Validate and normalize configuration
	err := normalizeConfig(&config)
	if err != nil {
		return nil, err
	}

	client := &RESTClient{
		config:     config,
	}

	if config.HTTPClient != nil {
		client.httpClient = config.HTTPClient
	} else {
		client.httpClient = &http.Client{
			Timeout: defaultTimeout,
		}
	}

	return client, nil
}

func (client *RESTClient) Register(instance *ServiceInstance) (*ServiceInstance, error) {
	// Record a pessimistic last heartbeat time - Better safe than sorry!
	lastHeartbeat := time.Now()

	url := fmt.Sprintf("%s/api/v1/instances", client.config.URL)
	body, err := client.doRequest("POST", url, instance, http.StatusCreated)
	if err != nil {
		return nil, err
	}

	m := make(map[string]interface{})
	err = json.Unmarshal(body, &m)
	if err != nil {
		return nil, newError(ErrorCodeInternalClientError, "error unmarshaling HTTP response body", err, "")
	}

	// TODO: recover type conversion panic
	registeredInstance := &*instance
	registeredInstance.ID = m["id"].(string)
	registeredInstance.TTL = int(m["ttl"].(float64))
	registeredInstance.LastHeartbeat = lastHeartbeat
	return registeredInstance, nil
}

func (client *RESTClient) Deregister(id string) error {
	uri := fmt.Sprintf("%s/api/v1/instances/%s", client.config.URL, id)
	_, err := client.doRequest("DELETE", uri, nil, http.StatusOK)
	return err
}

func (client *RESTClient) Renew(id string) error {
	uri := fmt.Sprintf("%s/api/v1/instances/%s/heartbeat", client.config.URL, id)
	_, err := client.doRequest("PUT", uri, nil, http.StatusOK)
	return err
}

func (client *RESTClient) ListServices() ([]string, error) {
	uri := fmt.Sprintf("%s/api/v1/services", client.config.URL)
	body, err := client.doRequest("GET", uri, nil, http.StatusOK)
	if err != nil {
		return nil, err
	}

	s := struct {
		Services []string `json:"services"`
	}{}
	err = json.Unmarshal(body, &s)
	if err != nil {
		return nil, newError(ErrorCodeInternalClientError, "error unmarshaling HTTP response body", err, "")
	}
	return s.Services, nil
}

func (client *RESTClient) ListServiceInstances(serviceName string) ([]*ServiceInstance, error) {
	return client.ListInstances(&InstanceFilter{
		ServiceName: serviceName,
	})
}

func (client *RESTClient) ListInstances(instanceFilter *InstanceFilter) ([]*ServiceInstance, error) {
	uri := fmt.Sprintf("%s/api/v1/instances", client.config.URL)

	if instanceFilter != nil {
		queryParams := instanceFilter.asQueryParams()
		if len(queryParams) > 0 {
			uri = fmt.Sprintf("%s?%s", uri, queryParams.Encode())
		}
		instanceFilter.asQueryParams()
	}

	body, err := client.doRequest("GET", uri, nil, http.StatusOK)
	if err != nil {
		return nil, err
	}

	s := struct {
		Instances []*ServiceInstance `json:"instances"`
	}{}
	err = json.Unmarshal(body, &s)
	if err != nil {
		return nil, newError(ErrorCodeInternalClientError, "error unmarshaling HTTP response body", err, "")
	}
	return s.Instances, nil
}

func (client *RESTClient) doRequest(method string, uri string, body interface{}, status int) ([]byte, error) {
	var reader io.Reader
	if body != nil {
		b, err := json.Marshal(body)
		if err != nil {
			return nil, newError(ErrorCodeInternalClientError, "error marshaling HTTP request body", err, "")
		}
		reader = bytes.NewBuffer(b)
	}

	req, err := http.NewRequest(method, uri, reader)
	if err != nil {
		return nil, newError(ErrorCodeInternalClientError, "error creating HTTP request", err, "")
	}

	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", client.config.AuthToken))
	if body != nil {
		// Body exists, and encoded as JSON
		req.Header.Set("Content-Type", "application/json")
	} else if method != "GET" {
		// No body, but the server needs to know that too
		req.Header.Set("Content-Length", "0")
	}

	resp, err := client.httpClient.Do(req)
	if err != nil {
		return nil, newError(ErrorCodeConnectionFailure, "error performing HTTP request", err, "")
	}

	defer resp.Body.Close()
	b, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, newError(ErrorCodeConnectionFailure, "error read HTTP response body", err, "")
	}

	if resp.StatusCode == status {
		return b, nil
	}

	requestID := resp.Header.Get("Sd-Request-Id")
	message := string(b)
	if requestID != "" {
		s := struct {
			Error string `json:"Error"`
		}{}
		err = json.Unmarshal(b, &s)
		if err != nil {
			message = s.Error
		}
	}
	switch resp.StatusCode {
	case http.StatusGone:
		return nil, newError(ErrorCodeUnknownInstance, message, nil, requestID)
	case http.StatusNotFound:
		if requestID != "" {
			return nil, newError(ErrorCodeInternalClientError, message, nil, requestID)
		} else {
			return nil, newError(ErrorCodeServiceUnavailable, message, nil, requestID)
		}
	case http.StatusBadGateway:
		return nil, newError(ErrorCodeServiceUnavailable, message, nil, requestID)
	case http.StatusUnauthorized:
		return nil, newError(ErrorCodeUnauthorized, message, nil, requestID)
	case http.StatusInternalServerError:
		return nil, newError(ErrorCodeInternalServerError, message, nil, requestID)
	default:
		return nil, newError(ErrorCodeInternalClientError, message, nil, requestID)
	}
}

func normalizeConfig(config *ClientConfig) error {
	if config == nil {
		return newError(ErrorCodeInvalidConfiguration, "client configuration cannot be nil", nil, "")
	}

	// Normalize server URL to not end with a "/"
	config.URL = strings.TrimRight(config.URL, "/")

	url, err := url.Parse(config.URL)
	if err != nil {
		return newError(ErrorCodeInvalidConfiguration, "cannot parse server URL", err, "")
	}

	if url.Scheme != "http" && url.Scheme != "https" {
		return newError(ErrorCodeInvalidConfiguration, fmt.Sprintf("unsupported scheme %s", url.Scheme), nil, "")
	}

	url.Query()
	if config.AuthToken == "" {
		return newError(ErrorCodeInvalidConfiguration, "missing authentication token", nil, "")
	}

	return nil
}
<commit_msg>Client: construct URLs using 'api/amalgam8' package methods<commit_after>// Copyright 2016 IBM Corporation
//
//   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 client

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"
	"time"
	"github.com/amalgam8/registry/api/protocol/amalgam8"
)

const defaultTimeout = time.Second * 30

// Client defines the interface used by clients of the Amalgam8 Service Registry.
type Client interface {
	Register(instance *ServiceInstance) (*ServiceInstance, error)
	Deregister(id string) error
	Renew(id string) error
	ListServices() ([]string, error)
	ListInstances(instanceFilter *InstanceFilter) ([]*ServiceInstance, error)
	ListServiceInstances(serviceName string) ([]*ServiceInstance, error)
}

type ClientConfig struct {
	URL       string
	AuthToken string
	HTTPClient *http.Client
}

// RESTClient implements the Client interface using Amalgam8 Service Registry REST API.
type RESTClient struct {
	config     ClientConfig
	httpClient *http.Client
}

func NewRESTClient(config ClientConfig) (*RESTClient, error) {
	// Validate and normalize configuration
	err := normalizeConfig(&config)
	if err != nil {
		return nil, err
	}

	client := &RESTClient{
		config:     config,
	}

	if config.HTTPClient != nil {
		client.httpClient = config.HTTPClient
	} else {
		client.httpClient = &http.Client{
			Timeout: defaultTimeout,
		}
	}

	return client, nil
}

func (client *RESTClient) Register(instance *ServiceInstance) (*ServiceInstance, error) {
	// Record a pessimistic last heartbeat time - Better safe than sorry!
	lastHeartbeat := time.Now()

	body, err := client.doRequest("POST", amalgam8.InstanceCreateURL(), instance, http.StatusCreated)
	if err != nil {
		return nil, err
	}

	m := make(map[string]interface{})
	err = json.Unmarshal(body, &m)
	if err != nil {
		return nil, newError(ErrorCodeInternalClientError, "error unmarshaling HTTP response body", err, "")
	}

	// TODO: recover type conversion panic
	registeredInstance := &*instance
	registeredInstance.ID = m["id"].(string)
	registeredInstance.TTL = int(m["ttl"].(float64))
	registeredInstance.LastHeartbeat = lastHeartbeat
	return registeredInstance, nil
}

func (client *RESTClient) Deregister(id string) error {
	_, err := client.doRequest("DELETE", amalgam8.InstanceURL(id), nil, http.StatusOK)
	return err
}

func (client *RESTClient) Renew(id string) error {
	_, err := client.doRequest("PUT", amalgam8.InstanceHeartbeatURL(id), nil, http.StatusOK)
	return err
}

func (client *RESTClient) ListServices() ([]string, error) {
	body, err := client.doRequest("GET", amalgam8.ServiceNamesURL(), nil, http.StatusOK)
	if err != nil {
		return nil, err
	}

	s := struct {
		Services []string `json:"services"`
	}{}
	err = json.Unmarshal(body, &s)
	if err != nil {
		return nil, newError(ErrorCodeInternalClientError, "error unmarshaling HTTP response body", err, "")
	}
	return s.Services, nil
}

func (client *RESTClient) ListServiceInstances(serviceName string) ([]*ServiceInstance, error) {
	return client.ListInstances(&InstanceFilter{
		ServiceName: serviceName,
	})
}

func (client *RESTClient) ListInstances(instanceFilter *InstanceFilter) ([]*ServiceInstance, error) {
	path := amalgam8.InstancesURL()
	if instanceFilter != nil {
		queryParams := instanceFilter.asQueryParams()
		if len(queryParams) > 0 {
			path = fmt.Sprintf("%s?%s", path, queryParams.Encode())
		}
		instanceFilter.asQueryParams()
	}

	body, err := client.doRequest("GET", path, nil, http.StatusOK)
	if err != nil {
		return nil, err
	}

	s := struct {
		Instances []*ServiceInstance `json:"instances"`
	}{}
	err = json.Unmarshal(body, &s)
	if err != nil {
		return nil, newError(ErrorCodeInternalClientError, "error unmarshaling HTTP response body", err, "")
	}
	return s.Instances, nil
}

func (client *RESTClient) doRequest(method string, path string, body interface{}, status int) ([]byte, error) {
	var reader io.Reader
	if body != nil {
		b, err := json.Marshal(body)
		if err != nil {
			return nil, newError(ErrorCodeInternalClientError, "error marshaling HTTP request body", err, "")
		}
		reader = bytes.NewBuffer(b)
	}

	uri := client.config.URL + path
	req, err := http.NewRequest(method, uri, reader)
	if err != nil {
		return nil, newError(ErrorCodeInternalClientError, "error creating HTTP request", err, "")
	}

	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", client.config.AuthToken))
	if body != nil {
		// Body exists, and encoded as JSON
		req.Header.Set("Content-Type", "application/json")
	} else if method != "GET" {
		// No body, but the server needs to know that too
		req.Header.Set("Content-Length", "0")
	}

	resp, err := client.httpClient.Do(req)
	if err != nil {
		return nil, newError(ErrorCodeConnectionFailure, "error performing HTTP request", err, "")
	}

	defer resp.Body.Close()
	b, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, newError(ErrorCodeConnectionFailure, "error read HTTP response body", err, "")
	}

	if resp.StatusCode == status {
		return b, nil
	}

	requestID := resp.Header.Get("Sd-Request-Id")
	message := string(b)
	if requestID != "" {
		s := struct {
			Error string `json:"Error"`
		}{}
		err = json.Unmarshal(b, &s)
		if err != nil {
			message = s.Error
		}
	}
	switch resp.StatusCode {
	case http.StatusGone:
		return nil, newError(ErrorCodeUnknownInstance, message, nil, requestID)
	case http.StatusNotFound:
		if requestID != "" {
			return nil, newError(ErrorCodeInternalClientError, message, nil, requestID)
		} else {
			return nil, newError(ErrorCodeServiceUnavailable, message, nil, requestID)
		}
	case http.StatusBadGateway:
		return nil, newError(ErrorCodeServiceUnavailable, message, nil, requestID)
	case http.StatusUnauthorized:
		return nil, newError(ErrorCodeUnauthorized, message, nil, requestID)
	case http.StatusInternalServerError:
		return nil, newError(ErrorCodeInternalServerError, message, nil, requestID)
	default:
		return nil, newError(ErrorCodeInternalClientError, message, nil, requestID)
	}
}

func normalizeConfig(config *ClientConfig) error {
	if config == nil {
		return newError(ErrorCodeInvalidConfiguration, "client configuration cannot be nil", nil, "")
	}

	// Normalize server URL to not end with a "/"
	config.URL = strings.TrimRight(config.URL, "/")

	url, err := url.Parse(config.URL)
	if err != nil {
		return newError(ErrorCodeInvalidConfiguration, "cannot parse server URL", err, "")
	}

	if url.Scheme != "http" && url.Scheme != "https" {
		return newError(ErrorCodeInvalidConfiguration, fmt.Sprintf("unsupported scheme %s", url.Scheme), nil, "")
	}

	url.Query()
	if config.AuthToken == "" {
		return newError(ErrorCodeInvalidConfiguration, "missing authentication token", nil, "")
	}

	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"math/rand"
	"net/http"
	"net/url"
	"os"
	"path"
	"strings"
	"time"

	"github.com/PonyvilleFM/aura/bot"
	"github.com/PonyvilleFM/aura/recording"
	"github.com/bwmarrin/discordgo"
	_ "github.com/joho/godotenv/autoload"
	hashids "github.com/speps/go-hashids"
)

type aura struct {
	cs *bot.CommandSet
	s  *discordgo.Session

	guildRecordings map[string]*recording.Recording
	state           *state
	hid             *hashids.HashID
}

type state struct {
	DownloadURLs map[string]string // Guild ID -> URL
	PermRoles    map[string]string // Guild ID -> needed role ID
	Shorturls    map[string]string // hashid -> partial route
}

func (s *state) Save() error {
	fout, err := os.Create(path.Join(dataPrefix, "state.json"))
	if err != nil {
		return err
	}
	defer fout.Close()

	return json.NewEncoder(fout).Encode(s)
}

func (s *state) Load() error {
	fin, err := os.Open(path.Join(dataPrefix, "state.json"))
	if err != nil {
		return err
	}
	defer fin.Close()

	return json.NewDecoder(fin).Decode(s)
}

const (
	djonHelp  = ``
	djoffHelp = ``
	setupHelp = ``
)

func (a *aura) Permissons(s *discordgo.Session, m *discordgo.Message, parv []string) error {
	ch, err := s.Channel(m.ChannelID)
	if err != nil {
		return err
	}

	gid := ch.GuildID
	role := a.state.PermRoles[gid]

	gu, err := s.GuildMember(gid, m.Author.ID)
	if err != nil {
		return err
	}

	found := false
	for _, r := range gu.Roles {
		if r == role {
			found = true
			break
		}
	}

	if !found {
		return errors.New("aura: no permissions")
	}

	return nil
}

func (a *aura) roles(s *discordgo.Session, m *discordgo.Message, parv []string) error {
	log.Println("got here")
	ch, err := s.Channel(m.ChannelID)
	if err != nil {
		return err
	}

	gid := ch.GuildID

	result := "Roles in this group:\n"

	roles, err := s.GuildRoles(gid)
	if err != nil {
		return err
	}

	for _, r := range roles {
		result += fmt.Sprintf("- %s: %s\n", r.ID, r.Name)
	}

	s.ChannelMessageSend(m.ChannelID, result)
	return nil
}

func (a *aura) setup(s *discordgo.Session, m *discordgo.Message, parv []string) error {
	if len(parv) != 3 {
		return errors.New("aura: wrong number of params for setup")
	}

	role := parv[1]
	url := parv[2]

	ch, err := s.Channel(m.ChannelID)
	if err != nil {
		return err
	}

	gid := ch.GuildID

	roles, err := s.GuildRoles(gid)
	if err != nil {
		return err
	}

	found := false
	for _, r := range roles {
		if r.ID == role {
			found = true
			break
		}
	}

	if !found {
		return errors.New("aura: Role not found")
	}

	a.state.PermRoles[gid] = role
	a.state.DownloadURLs[gid] = url

	s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("Guild %s set up for recording url %s controlled by role %s", gid, url, role))

	a.state.Save()
	return nil
}

func (a *aura) djon(s *discordgo.Session, m *discordgo.Message, parv []string) error {
	fname := fmt.Sprintf("%s - %s.mp3", m.Author.Username, time.Now().Format(time.ANSIC))

	ch, err := s.Channel(m.ChannelID)
	if err != nil {
		return err
	}

	gid := ch.GuildID

	_, ok := a.guildRecordings[gid]
	if ok {
		return errors.New("aura: another recording is already in progress")
	}

	os.Mkdir(path.Join(dataPrefix, gid), 0775)

	r, err := recording.New(a.state.DownloadURLs[gid], path.Join(dataPrefix, gid, fname))
	if err != nil {
		return err
	}

	a.guildRecordings[gid] = r
	go r.Start()

	s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("Now recording: %s", fname))

	return nil
}

func (a *aura) djoff(s *discordgo.Session, m *discordgo.Message, parv []string) error {
	ch, err := s.Channel(m.ChannelID)
	if err != nil {
		return err
	}

	gid := ch.GuildID

	urlencode := func(inp string) string {
		return (&url.URL{Path: inp}).String()
	}

	r, ok := a.guildRecordings[gid]
	if !ok {
		return errors.New("aura: no recording is currently in progress")
	}

	s.ChannelMessageSend(m.ChannelID, "Finishing recording (waiting 30 seconds)")
	go func() {
		time.Sleep(30 * time.Second)

		r.Cancel()
		<-r.Done()

		fname := r.OutputFilename()
		parts := strings.Split(fname, "/")

		recurl := fmt.Sprintf("https://%s/var/%s/%s", recordingDomain, parts[1], urlencode(parts[2]))
		id, err := a.hid.EncodeInt64([]int64{rand.Int63()})
		if err != nil {
			s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("This state should be impossible. Recording saved but unknown short URL: %v", err))
			return
		}

		a.state.Shorturls[id] = recurl
		a.state.Save()

		s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("Recording complete (%s): https://%s/id/%s", time.Now().Sub(r.StartTime()).String(), recordingDomain, id))

		a.guildRecordings[gid] = nil
	}()
	return nil
}

func (a *aura) Handle(s *discordgo.Session, m *discordgo.MessageCreate) {
	err := a.cs.Run(s, m.Message)
	if err != nil {
		log.Println(err)
	}
}

var (
	token           = os.Getenv("TOKEN")
	dataPrefix      = os.Getenv("DATA_PREFIX")
	recordingDomain = os.Getenv("RECORDING_DOMAIN")
	hashidsSalt     = os.Getenv("HASHIDS_SALT")
	port            = os.Getenv("PORT")
)

func main() {
	dg, err := discordgo.New("Bot " + token)
	if err != nil {
		log.Fatal(err)
	}

	hid := hashids.NewData()
	hid.Salt = hashidsSalt

	a := &aura{
		cs:              bot.NewCommandSet(),
		s:               dg,
		guildRecordings: map[string]*recording.Recording{},

		hid: hashids.NewWithData(hid),

		state: &state{
			DownloadURLs: map[string]string{},
			PermRoles:    map[string]string{},
			Shorturls:    map[string]string{},
		},
	}

	err = a.state.Load()
	if err != nil {
		log.Println(err)
	}

	a.cs.Add(bot.NewBasicCommand("roles", "", bot.NoPermissions, a.roles))
	a.cs.Add(bot.NewBasicCommand("setup", setupHelp, bot.NoPermissions, a.setup))
	a.cs.Add(bot.NewBasicCommand("djon", djonHelp, a.Permissons, a.djon))
	a.cs.Add(bot.NewBasicCommand("djoff", djoffHelp, a.Permissons, a.djoff))

	dg.AddHandler(a.Handle)
	dg.AddHandler(messageCreate)

	err = dg.Open()
	if err != nil {
		log.Fatal(err)
	}

	log.Println("ready")

	http.Handle("/id/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		id := r.RequestURI[4:]

		redir, ok := a.state.Shorturls[id]
		if !ok {
			http.Error(w, "not found, sorry", http.StatusNotFound)
			return
		}

		http.Redirect(w, r, redir, http.StatusFound)
	}))

	http.HandleFunc("/links.json", func(w http.ResponseWriter, r *http.Request) {
		json.NewEncoder(w).Encode(a.state.Shorturls)
	})

	http.Handle("/var/", http.FileServer(http.Dir(".")))

	http.ListenAndServe(":"+port, nil)
}

// This function will be called (due to AddHandler above) every time a new
// message is created on any channel that the autenticated bot has access to.
func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) {
	// Print message to stdout.
	fmt.Printf("%20s %20s %20s > %s\n", m.ChannelID, time.Now().Format(time.Stamp), m.Author.Username, m.Content)
}
<commit_msg>cmd/aura: don't falsely report a recording being in progress if one isn't in progress<commit_after>package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"math/rand"
	"net/http"
	"net/url"
	"os"
	"path"
	"strings"
	"time"

	"github.com/PonyvilleFM/aura/bot"
	"github.com/PonyvilleFM/aura/recording"
	"github.com/bwmarrin/discordgo"
	_ "github.com/joho/godotenv/autoload"
	hashids "github.com/speps/go-hashids"
)

type aura struct {
	cs *bot.CommandSet
	s  *discordgo.Session

	guildRecordings map[string]*recording.Recording
	state           *state
	hid             *hashids.HashID
}

type state struct {
	DownloadURLs map[string]string // Guild ID -> URL
	PermRoles    map[string]string // Guild ID -> needed role ID
	Shorturls    map[string]string // hashid -> partial route
}

func (s *state) Save() error {
	fout, err := os.Create(path.Join(dataPrefix, "state.json"))
	if err != nil {
		return err
	}
	defer fout.Close()

	return json.NewEncoder(fout).Encode(s)
}

func (s *state) Load() error {
	fin, err := os.Open(path.Join(dataPrefix, "state.json"))
	if err != nil {
		return err
	}
	defer fin.Close()

	return json.NewDecoder(fin).Decode(s)
}

const (
	djonHelp  = ``
	djoffHelp = ``
	setupHelp = ``
)

func (a *aura) Permissons(s *discordgo.Session, m *discordgo.Message, parv []string) error {
	ch, err := s.Channel(m.ChannelID)
	if err != nil {
		return err
	}

	gid := ch.GuildID
	role := a.state.PermRoles[gid]

	gu, err := s.GuildMember(gid, m.Author.ID)
	if err != nil {
		return err
	}

	found := false
	for _, r := range gu.Roles {
		if r == role {
			found = true
			break
		}
	}

	if !found {
		return errors.New("aura: no permissions")
	}

	return nil
}

func (a *aura) roles(s *discordgo.Session, m *discordgo.Message, parv []string) error {
	log.Println("got here")
	ch, err := s.Channel(m.ChannelID)
	if err != nil {
		return err
	}

	gid := ch.GuildID

	result := "Roles in this group:\n"

	roles, err := s.GuildRoles(gid)
	if err != nil {
		return err
	}

	for _, r := range roles {
		result += fmt.Sprintf("- %s: %s\n", r.ID, r.Name)
	}

	s.ChannelMessageSend(m.ChannelID, result)
	return nil
}

func (a *aura) setup(s *discordgo.Session, m *discordgo.Message, parv []string) error {
	if len(parv) != 3 {
		return errors.New("aura: wrong number of params for setup")
	}

	role := parv[1]
	url := parv[2]

	ch, err := s.Channel(m.ChannelID)
	if err != nil {
		return err
	}

	gid := ch.GuildID

	roles, err := s.GuildRoles(gid)
	if err != nil {
		return err
	}

	found := false
	for _, r := range roles {
		if r.ID == role {
			found = true
			break
		}
	}

	if !found {
		return errors.New("aura: Role not found")
	}

	a.state.PermRoles[gid] = role
	a.state.DownloadURLs[gid] = url

	s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("Guild %s set up for recording url %s controlled by role %s", gid, url, role))

	a.state.Save()
	return nil
}

func (a *aura) djon(s *discordgo.Session, m *discordgo.Message, parv []string) error {
	fname := fmt.Sprintf("%s - %s.mp3", m.Author.Username, time.Now().Format(time.ANSIC))

	ch, err := s.Channel(m.ChannelID)
	if err != nil {
		return err
	}

	gid := ch.GuildID

	r, ok := a.guildRecordings[gid]
	if r != nil || ok {
		log.Println(a.guildRecordings)
		return errors.New("aura: another recording is already in progress")
	}

	os.Mkdir(path.Join(dataPrefix, gid), 0775)

	r, err = recording.New(a.state.DownloadURLs[gid], path.Join(dataPrefix, gid, fname))
	if err != nil {
		return err
	}

	a.guildRecordings[gid] = r
	go r.Start()

	s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("Now recording: %s", fname))

	return nil
}

func (a *aura) djoff(s *discordgo.Session, m *discordgo.Message, parv []string) error {
	ch, err := s.Channel(m.ChannelID)
	if err != nil {
		return err
	}

	gid := ch.GuildID

	urlencode := func(inp string) string {
		return (&url.URL{Path: inp}).String()
	}

	r, ok := a.guildRecordings[gid]
	if r == nil || !ok {
		log.Println(a.guildRecordings)
		return errors.New("aura: no recording is currently in progress")
	}

	s.ChannelMessageSend(m.ChannelID, "Finishing recording (waiting 30 seconds)")
	go func() {
		time.Sleep(30 * time.Second)

		r.Cancel()
		<-r.Done()

		fname := r.OutputFilename()
		parts := strings.Split(fname, "/")

		recurl := fmt.Sprintf("https://%s/var/%s/%s", recordingDomain, parts[1], urlencode(parts[2]))
		id, err := a.hid.EncodeInt64([]int64{rand.Int63()})
		if err != nil {
			s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("This state should be impossible. Recording saved but unknown short URL: %v", err))
			return
		}

		a.state.Shorturls[id] = recurl
		a.state.Save()

		s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("Recording complete (%s): https://%s/id/%s", time.Now().Sub(r.StartTime()).String(), recordingDomain, id))

		delete(a.guildRecordings, gid)
	}()

	return nil
}

func (a *aura) Handle(s *discordgo.Session, m *discordgo.MessageCreate) {
	err := a.cs.Run(s, m.Message)
	if err != nil {
		log.Println(err)
	}
}

var (
	token           = os.Getenv("TOKEN")
	dataPrefix      = os.Getenv("DATA_PREFIX")
	recordingDomain = os.Getenv("RECORDING_DOMAIN")
	hashidsSalt     = os.Getenv("HASHIDS_SALT")
	port            = os.Getenv("PORT")
)

func main() {
	dg, err := discordgo.New("Bot " + token)
	if err != nil {
		log.Fatal(err)
	}

	hid := hashids.NewData()
	hid.Salt = hashidsSalt

	a := &aura{
		cs:              bot.NewCommandSet(),
		s:               dg,
		guildRecordings: map[string]*recording.Recording{},

		hid: hashids.NewWithData(hid),

		state: &state{
			DownloadURLs: map[string]string{},
			PermRoles:    map[string]string{},
			Shorturls:    map[string]string{},
		},
	}

	err = a.state.Load()
	if err != nil {
		log.Println(err)
	}

	a.cs.Add(bot.NewBasicCommand("roles", "", bot.NoPermissions, a.roles))
	a.cs.Add(bot.NewBasicCommand("setup", setupHelp, bot.NoPermissions, a.setup))
	a.cs.Add(bot.NewBasicCommand("djon", djonHelp, a.Permissons, a.djon))
	a.cs.Add(bot.NewBasicCommand("djoff", djoffHelp, a.Permissons, a.djoff))

	dg.AddHandler(a.Handle)
	dg.AddHandler(messageCreate)

	err = dg.Open()
	if err != nil {
		log.Fatal(err)
	}

	log.Println("ready")

	http.Handle("/id/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		id := r.RequestURI[4:]

		redir, ok := a.state.Shorturls[id]
		if !ok {
			http.Error(w, "not found, sorry", http.StatusNotFound)
			return
		}

		http.Redirect(w, r, redir, http.StatusFound)
	}))

	http.HandleFunc("/links.json", func(w http.ResponseWriter, r *http.Request) {
		json.NewEncoder(w).Encode(a.state.Shorturls)
	})

	http.Handle("/var/", http.FileServer(http.Dir(".")))

	http.ListenAndServe(":"+port, nil)
}

// This function will be called (due to AddHandler above) every time a new
// message is created on any channel that the autenticated bot has access to.
func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) {
	// Print message to stdout.
	fmt.Printf("%20s %20s %20s > %s\n", m.ChannelID, time.Now().Format(time.Stamp), m.Author.Username, m.Content)
}
<|endoftext|>


<commit_before>// Package client provides a BitTorrent client implementation that is capable of downlaoding multiple torrents in parallel.
package client

import (
	"bytes"
	"errors"
	"io"
	"net"
	"os"
	"path/filepath"
	"strconv"
	"sync"
	"time"

	"github.com/boltdb/bolt"
	"github.com/cenkalti/rain/internal/logger"
	"github.com/cenkalti/rain/torrent"
	"github.com/cenkalti/rain/torrent/resumer/boltdbresumer"
	"github.com/cenkalti/rain/torrent/storage"
	"github.com/cenkalti/rain/torrent/storage/filestorage"
	"github.com/mitchellh/go-homedir"
	"github.com/nictuku/dht"
)

var mainBucket = []byte("torrents")

type Client struct {
	config Config
	db     *bolt.DB
	log    logger.Logger
	dht    *dht.DHT
	closeC chan struct{}

	mPeerRequests   sync.Mutex
	dhtPeerRequests map[dht.InfoHash]struct{}

	m                  sync.RWMutex
	torrents           map[uint64]*Torrent
	torrentsByInfoHash map[dht.InfoHash][]*Torrent

	mPorts         sync.Mutex
	availablePorts map[uint16]struct{}
}

// New returns a pointer to new Rain BitTorrent client.
func New(cfg Config) (*Client, error) {
	if cfg.PortBegin >= cfg.PortEnd {
		return nil, errors.New("invalid port range")
	}
	var err error
	cfg.Database, err = homedir.Expand(cfg.Database)
	if err != nil {
		return nil, err
	}
	cfg.DataDir, err = homedir.Expand(cfg.DataDir)
	if err != nil {
		return nil, err
	}
	err = os.MkdirAll(filepath.Dir(cfg.Database), 0750)
	if err != nil {
		return nil, err
	}
	db, err := bolt.Open(cfg.Database, 0640, &bolt.Options{Timeout: time.Second})
	if err == bolt.ErrTimeout {
		return nil, errors.New("resume database is locked by another process")
	} else if err != nil {
		return nil, err
	}
	defer func() {
		if err != nil {
			db.Close()
		}
	}()
	l := logger.New("client")
	var ids []uint64
	err = db.Update(func(tx *bolt.Tx) error {
		b, err2 := tx.CreateBucketIfNotExists(mainBucket)
		if err2 != nil {
			return err2
		}
		return b.ForEach(func(k, _ []byte) error {
			id, err3 := strconv.ParseUint(string(k), 10, 64)
			if err3 != nil {
				l.Error(err3)
				return nil
			}
			ids = append(ids, id)
			return nil
		})
	})
	if err != nil {
		return nil, err
	}
	dhtConfig := dht.NewConfig()
	dhtConfig.Address = cfg.DHTAddress
	dhtConfig.Port = int(cfg.DHTPort)
	dhtNode, err := dht.New(dhtConfig)
	if err != nil {
		return nil, err
	}
	err = dhtNode.Start()
	if err != nil {
		return nil, err
	}
	ports := make(map[uint16]struct{})
	for p := cfg.PortBegin; p < cfg.PortEnd; p++ {
		ports[p] = struct{}{}
	}
	c := &Client{
		config:             cfg,
		db:                 db,
		log:                l,
		torrents:           make(map[uint64]*Torrent),
		torrentsByInfoHash: make(map[dht.InfoHash][]*Torrent),
		availablePorts:     ports,
		dht:                dhtNode,
		dhtPeerRequests:    make(map[dht.InfoHash]struct{}),
		closeC:             make(chan struct{}),
	}
	err = c.loadExistingTorrents(ids)
	if err != nil {
		return nil, err
	}
	go c.processDHTResults()
	return c, nil
}

func (c *Client) processDHTResults() {
	dhtLimiter := time.NewTicker(time.Second)
	defer dhtLimiter.Stop()
	for {
		select {
		case <-dhtLimiter.C:
			c.handleDHTtick()
		case res := <-c.dht.PeersRequestResults:
			for ih, peers := range res {
				torrents, ok := c.torrentsByInfoHash[ih]
				if !ok {
					continue
				}
				addrs := parseDHTPeers(peers)
				for _, t := range torrents {
					select {
					case t.dhtAnnouncer.peersC <- addrs:
					case <-t.removed:
					}
				}
			}
		case <-c.closeC:
			return
		}
	}
}

func (c *Client) handleDHTtick() {
	c.mPeerRequests.Lock()
	defer c.mPeerRequests.Unlock()
	if len(c.dhtPeerRequests) == 0 {
		return
	}
	var ih dht.InfoHash
	found := false
	for ih = range c.dhtPeerRequests {
		found = true
		break
	}
	if !found {
		return
	}
	c.dht.PeersRequest(string(ih), true)
	delete(c.dhtPeerRequests, ih)
}

func parseDHTPeers(peers []string) []*net.TCPAddr {
	var addrs []*net.TCPAddr
	for _, peer := range peers {
		if len(peer) != 6 {
			// only IPv4 is supported for now
			continue
		}
		addr := &net.TCPAddr{
			IP:   net.IP(peer[:4]),
			Port: int((uint16(peer[4]) << 8) | uint16(peer[5])),
		}
		addrs = append(addrs, addr)
	}
	return addrs
}

func (c *Client) loadExistingTorrents(ids []uint64) error {
	var started []*Torrent
	for _, id := range ids {
		res, err := boltdbresumer.New(c.db, mainBucket, []byte(strconv.FormatUint(id, 10)))
		if err != nil {
			c.log.Error(err)
			continue
		}
		hasStarted, err := c.hasStarted(id)
		if err != nil {
			c.log.Error(err)
			continue
		}
		t, spec, err := torrent.NewResume(res, c.config.Torrent)
		if err != nil {
			c.log.Error(err)
			continue
		}
		delete(c.availablePorts, uint16(spec.Port))

		t2 := &Torrent{
			client:  c,
			torrent: t,
			id:      id,
			port:    uint16(spec.Port),
			removed: make(chan struct{}),
		}
		t2.setDHTNode()

		c.torrents[id] = t2
		ih := dht.InfoHash(t.InfoHashBytes())
		c.torrentsByInfoHash[ih] = append(c.torrentsByInfoHash[ih], t2)
		c.log.Infof("loaded existing torrent: #%d %s", id, t.Name())
		if hasStarted {
			started = append(started, t2)
		}
	}
	for _, t := range started {
		t.Start()
	}
	return nil
}

func (c *Client) hasStarted(id uint64) (bool, error) {
	subBucket := strconv.FormatUint(id, 10)
	started := false
	err := c.db.View(func(tx *bolt.Tx) error {
		b := tx.Bucket(mainBucket).Bucket([]byte(subBucket))
		val := b.Get([]byte("started"))
		if bytes.Equal(val, []byte("1")) {
			started = true
		}
		return nil
	})
	return started, err
}

func (c *Client) Close() error {
	c.dht.Stop()

	c.m.Lock()
	for _, t := range c.torrents {
		t.torrent.Close()
	}
	c.torrents = nil
	c.m.Unlock()

	return c.db.Close()
}

func (c *Client) ListTorrents() []*Torrent {
	c.m.RLock()
	defer c.m.RUnlock()
	torrents := make([]*Torrent, 0, len(c.torrents))
	for _, t := range c.torrents {
		torrents = append(torrents, t)
	}
	return torrents
}

func (c *Client) AddTorrent(r io.Reader) (*Torrent, error) {
	return c.add(func(port int, sto storage.Storage) (*torrent.Torrent, error) {
		return torrent.New(r, port, sto, c.config.Torrent)
	})
}

func (c *Client) AddMagnet(link string) (*Torrent, error) {
	return c.add(func(port int, sto storage.Storage) (*torrent.Torrent, error) {
		return torrent.NewMagnet(link, port, sto, c.config.Torrent)
	})
}

func (c *Client) add(f func(port int, sto storage.Storage) (*torrent.Torrent, error)) (*Torrent, error) {
	port, err := c.getPort()
	if err != nil {
		return nil, err
	}
	defer func() {
		if err != nil {
			c.releasePort(port)
		}
	}()

	var id uint64
	err = c.db.Update(func(tx *bolt.Tx) error {
		bucket := tx.Bucket(mainBucket)
		id, _ = bucket.NextSequence()
		return nil
	})
	if err != nil {
		return nil, err
	}

	dest := filepath.Join(c.config.DataDir, strconv.FormatUint(id, 10))
	sto, err := filestorage.New(dest)
	if err != nil {
		return nil, err
	}

	t, err := f(int(port), sto)
	if err != nil {
		return nil, err
	}
	defer func() {
		if err != nil {
			t.Close()
		}
	}()

	subBucket := strconv.FormatUint(id, 10)
	res, err := boltdbresumer.New(c.db, mainBucket, []byte(subBucket))
	if err != nil {
		return nil, err
	}
	_, err = t.SetResume(res)
	if err != nil {
		return nil, err
	}

	t2 := &Torrent{
		client:  c,
		torrent: t,
		id:      id,
		port:    port,
		removed: make(chan struct{}),
	}
	t2.setDHTNode()

	err = t2.Start()
	if err != nil {
		return nil, err
	}

	c.m.Lock()
	defer c.m.Unlock()
	c.torrents[id] = t2
	ih := dht.InfoHash(t.InfoHashBytes())
	c.torrentsByInfoHash[ih] = append(c.torrentsByInfoHash[ih], t2)
	return t2, nil
}

func (c *Client) getPort() (uint16, error) {
	c.mPorts.Lock()
	defer c.mPorts.Unlock()
	for p := range c.availablePorts {
		delete(c.availablePorts, p)
		return p, nil
	}
	return 0, errors.New("no free port")
}

func (c *Client) releasePort(port uint16) {
	c.mPorts.Lock()
	defer c.mPorts.Unlock()
	c.availablePorts[port] = struct{}{}
}

func (c *Client) GetTorrent(id uint64) *Torrent {
	c.m.RLock()
	defer c.m.RUnlock()
	return c.torrents[id]
}

func (c *Client) RemoveTorrent(id uint64) error {
	c.m.Lock()
	defer c.m.Unlock()
	t, ok := c.torrents[id]
	if !ok {
		return nil
	}
	close(t.removed)
	t.torrent.Close()
	delete(c.torrents, id)
	delete(c.torrentsByInfoHash, dht.InfoHash(t.torrent.InfoHashBytes()))
	c.releasePort(t.port)
	subBucket := strconv.FormatUint(id, 10)
	return c.db.Update(func(tx *bolt.Tx) error {
		return tx.Bucket(mainBucket).DeleteBucket([]byte(subBucket))
	})
}
<commit_msg>refactor<commit_after>// Package client provides a BitTorrent client implementation that is capable of downlaoding multiple torrents in parallel.
package client

import (
	"bytes"
	"errors"
	"io"
	"net"
	"os"
	"path/filepath"
	"strconv"
	"sync"
	"time"

	"github.com/boltdb/bolt"
	"github.com/cenkalti/rain/internal/logger"
	"github.com/cenkalti/rain/torrent"
	"github.com/cenkalti/rain/torrent/resumer/boltdbresumer"
	"github.com/cenkalti/rain/torrent/storage"
	"github.com/cenkalti/rain/torrent/storage/filestorage"
	"github.com/mitchellh/go-homedir"
	"github.com/nictuku/dht"
)

var mainBucket = []byte("torrents")

type Client struct {
	config Config
	db     *bolt.DB
	log    logger.Logger
	dht    *dht.DHT
	closeC chan struct{}

	mPeerRequests   sync.Mutex
	dhtPeerRequests map[dht.InfoHash]struct{}

	m                  sync.RWMutex
	torrents           map[uint64]*Torrent
	torrentsByInfoHash map[dht.InfoHash][]*Torrent

	mPorts         sync.Mutex
	availablePorts map[uint16]struct{}
}

// New returns a pointer to new Rain BitTorrent client.
func New(cfg Config) (*Client, error) {
	if cfg.PortBegin >= cfg.PortEnd {
		return nil, errors.New("invalid port range")
	}
	var err error
	cfg.Database, err = homedir.Expand(cfg.Database)
	if err != nil {
		return nil, err
	}
	cfg.DataDir, err = homedir.Expand(cfg.DataDir)
	if err != nil {
		return nil, err
	}
	err = os.MkdirAll(filepath.Dir(cfg.Database), 0750)
	if err != nil {
		return nil, err
	}
	db, err := bolt.Open(cfg.Database, 0640, &bolt.Options{Timeout: time.Second})
	if err == bolt.ErrTimeout {
		return nil, errors.New("resume database is locked by another process")
	} else if err != nil {
		return nil, err
	}
	defer func() {
		if err != nil {
			db.Close()
		}
	}()
	l := logger.New("client")
	var ids []uint64
	err = db.Update(func(tx *bolt.Tx) error {
		b, err2 := tx.CreateBucketIfNotExists(mainBucket)
		if err2 != nil {
			return err2
		}
		return b.ForEach(func(k, _ []byte) error {
			id, err3 := strconv.ParseUint(string(k), 10, 64)
			if err3 != nil {
				l.Error(err3)
				return nil
			}
			ids = append(ids, id)
			return nil
		})
	})
	if err != nil {
		return nil, err
	}
	dhtConfig := dht.NewConfig()
	dhtConfig.Address = cfg.DHTAddress
	dhtConfig.Port = int(cfg.DHTPort)
	dhtNode, err := dht.New(dhtConfig)
	if err != nil {
		return nil, err
	}
	err = dhtNode.Start()
	if err != nil {
		return nil, err
	}
	ports := make(map[uint16]struct{})
	for p := cfg.PortBegin; p < cfg.PortEnd; p++ {
		ports[p] = struct{}{}
	}
	c := &Client{
		config:             cfg,
		db:                 db,
		log:                l,
		torrents:           make(map[uint64]*Torrent),
		torrentsByInfoHash: make(map[dht.InfoHash][]*Torrent),
		availablePorts:     ports,
		dht:                dhtNode,
		dhtPeerRequests:    make(map[dht.InfoHash]struct{}),
		closeC:             make(chan struct{}),
	}
	err = c.loadExistingTorrents(ids)
	if err != nil {
		return nil, err
	}
	go c.processDHTResults()
	return c, nil
}

func (c *Client) processDHTResults() {
	dhtLimiter := time.NewTicker(time.Second)
	defer dhtLimiter.Stop()
	for {
		select {
		case <-dhtLimiter.C:
			c.handleDHTtick()
		case res := <-c.dht.PeersRequestResults:
			for ih, peers := range res {
				torrents, ok := c.torrentsByInfoHash[ih]
				if !ok {
					continue
				}
				addrs := parseDHTPeers(peers)
				for _, t := range torrents {
					select {
					case t.dhtAnnouncer.peersC <- addrs:
					case <-t.removed:
					}
				}
			}
		case <-c.closeC:
			return
		}
	}
}

func (c *Client) handleDHTtick() {
	c.mPeerRequests.Lock()
	defer c.mPeerRequests.Unlock()
	if len(c.dhtPeerRequests) == 0 {
		return
	}
	var ih dht.InfoHash
	found := false
	for ih = range c.dhtPeerRequests {
		found = true
		break
	}
	if !found {
		return
	}
	c.dht.PeersRequest(string(ih), true)
	delete(c.dhtPeerRequests, ih)
}

func parseDHTPeers(peers []string) []*net.TCPAddr {
	var addrs []*net.TCPAddr
	for _, peer := range peers {
		if len(peer) != 6 {
			// only IPv4 is supported for now
			continue
		}
		addr := &net.TCPAddr{
			IP:   net.IP(peer[:4]),
			Port: int((uint16(peer[4]) << 8) | uint16(peer[5])),
		}
		addrs = append(addrs, addr)
	}
	return addrs
}

func (c *Client) loadExistingTorrents(ids []uint64) error {
	var started []*Torrent
	for _, id := range ids {
		res, err := boltdbresumer.New(c.db, mainBucket, []byte(strconv.FormatUint(id, 10)))
		if err != nil {
			c.log.Error(err)
			continue
		}
		hasStarted, err := c.hasStarted(id)
		if err != nil {
			c.log.Error(err)
			continue
		}
		t, spec, err := torrent.NewResume(res, c.config.Torrent)
		if err != nil {
			c.log.Error(err)
			continue
		}
		delete(c.availablePorts, uint16(spec.Port))

		t2 := c.newTorrent(t, id, uint16(spec.Port))
		c.log.Infof("loaded existing torrent: #%d %s", id, t.Name())
		if hasStarted {
			started = append(started, t2)
		}
	}
	for _, t := range started {
		t.Start()
	}
	return nil
}

func (c *Client) hasStarted(id uint64) (bool, error) {
	subBucket := strconv.FormatUint(id, 10)
	started := false
	err := c.db.View(func(tx *bolt.Tx) error {
		b := tx.Bucket(mainBucket).Bucket([]byte(subBucket))
		val := b.Get([]byte("started"))
		if bytes.Equal(val, []byte("1")) {
			started = true
		}
		return nil
	})
	return started, err
}

func (c *Client) Close() error {
	c.dht.Stop()

	c.m.Lock()
	for _, t := range c.torrents {
		t.torrent.Close()
	}
	c.torrents = nil
	c.m.Unlock()

	return c.db.Close()
}

func (c *Client) ListTorrents() []*Torrent {
	c.m.RLock()
	defer c.m.RUnlock()
	torrents := make([]*Torrent, 0, len(c.torrents))
	for _, t := range c.torrents {
		torrents = append(torrents, t)
	}
	return torrents
}

func (c *Client) AddTorrent(r io.Reader) (*Torrent, error) {
	return c.add(func(port int, sto storage.Storage) (*torrent.Torrent, error) {
		return torrent.New(r, port, sto, c.config.Torrent)
	})
}

func (c *Client) AddMagnet(link string) (*Torrent, error) {
	return c.add(func(port int, sto storage.Storage) (*torrent.Torrent, error) {
		return torrent.NewMagnet(link, port, sto, c.config.Torrent)
	})
}

func (c *Client) add(f func(port int, sto storage.Storage) (*torrent.Torrent, error)) (*Torrent, error) {
	port, err := c.getPort()
	if err != nil {
		return nil, err
	}
	defer func() {
		if err != nil {
			c.releasePort(port)
		}
	}()

	var id uint64
	err = c.db.Update(func(tx *bolt.Tx) error {
		bucket := tx.Bucket(mainBucket)
		id, _ = bucket.NextSequence()
		return nil
	})
	if err != nil {
		return nil, err
	}

	dest := filepath.Join(c.config.DataDir, strconv.FormatUint(id, 10))
	sto, err := filestorage.New(dest)
	if err != nil {
		return nil, err
	}

	t, err := f(int(port), sto)
	if err != nil {
		return nil, err
	}
	defer func() {
		if err != nil {
			t.Close()
		}
	}()

	subBucket := strconv.FormatUint(id, 10)
	res, err := boltdbresumer.New(c.db, mainBucket, []byte(subBucket))
	if err != nil {
		return nil, err
	}
	_, err = t.SetResume(res)
	if err != nil {
		return nil, err
	}

	t2 := c.newTorrent(t, id, port)

	return t2, t2.Start()
}

func (c *Client) newTorrent(t *torrent.Torrent, id uint64, port uint16) *Torrent {
	t2 := &Torrent{
		client:  c,
		torrent: t,
		id:      id,
		port:    port,
		removed: make(chan struct{}),
	}
	t2.setDHTNode()

	c.m.Lock()
	defer c.m.Unlock()
	c.torrents[id] = t2
	ih := dht.InfoHash(t.InfoHashBytes())
	c.torrentsByInfoHash[ih] = append(c.torrentsByInfoHash[ih], t2)
	return t2
}

func (c *Client) getPort() (uint16, error) {
	c.mPorts.Lock()
	defer c.mPorts.Unlock()
	for p := range c.availablePorts {
		delete(c.availablePorts, p)
		return p, nil
	}
	return 0, errors.New("no free port")
}

func (c *Client) releasePort(port uint16) {
	c.mPorts.Lock()
	defer c.mPorts.Unlock()
	c.availablePorts[port] = struct{}{}
}

func (c *Client) GetTorrent(id uint64) *Torrent {
	c.m.RLock()
	defer c.m.RUnlock()
	return c.torrents[id]
}

func (c *Client) RemoveTorrent(id uint64) error {
	c.m.Lock()
	defer c.m.Unlock()
	t, ok := c.torrents[id]
	if !ok {
		return nil
	}
	close(t.removed)
	t.torrent.Close()
	delete(c.torrents, id)
	delete(c.torrentsByInfoHash, dht.InfoHash(t.torrent.InfoHashBytes()))
	c.releasePort(t.port)
	subBucket := strconv.FormatUint(id, 10)
	return c.db.Update(func(tx *bolt.Tx) error {
		return tx.Bucket(mainBucket).DeleteBucket([]byte(subBucket))
	})
}
<|endoftext|>


<commit_before>package cmd

import (
	"bytes"
	. "launchpad.net/gocheck"
	"net/http"
	"os"
	"syscall"
)

func patchStdin(c *C, content []byte) {
	f, err := os.OpenFile("/tmp/passwdfile.txt", syscall.O_RDWR|syscall.O_NDELAY|syscall.O_CREAT|syscall.O_TRUNC, 0600)
	c.Assert(err, IsNil)
	n, err := f.Write(content)
	c.Assert(err, IsNil)
	c.Assert(n, Equals, len(content))
	ret, err := f.Seek(0, 0)
	c.Assert(err, IsNil)
	c.Assert(ret, Equals, int64(0))
	os.Stdin = f
}

func unpathStdin() {
	os.Stdin = os.NewFile(uintptr(syscall.Stdin), "/dev/stdin")
}

func (s *S) TestLogin(c *C) {
	patchStdin(c, []byte("chico\n"))
	defer unpathStdin()
	expected := "Password: \nSuccessfully logged!\n"
	context := Context{[]string{}, []string{"foo@foo.com"}, manager.Stdout, manager.Stderr}
	client := NewClient(&http.Client{Transport: &transport{msg: `{"token": "sometoken"}`, status: http.StatusOK}})
	command := Login{}
	err := command.Run(&context, client)
	c.Assert(err, IsNil)
	c.Assert(manager.Stdout.(*bytes.Buffer).String(), Equals, expected)

	token, err := ReadToken()
	c.Assert(token, Equals, "sometoken")
}

func (s *S) TestLoginShouldReturnErrorIfThePasswordIsNotGiven(c *C) {
	patchStdin(c, []byte("\n"))
	defer unpathStdin()
	expected := "Password: \nYou must provide the password!\n"
	context := Context{[]string{}, []string{"foo@foo.com"}, manager.Stdout, manager.Stderr}
	command := Login{}
	err := command.Run(&context, nil)
	c.Assert(err, NotNil)
	c.Assert(manager.Stdout.(*bytes.Buffer).String(), Equals, expected)
}

func (s *S) TestAddKey(c *C) {
	expected := "Key added with success!\n"
	context := Context{[]string{}, []string{}, manager.Stdout, manager.Stderr}
	client := NewClient(&http.Client{Transport: &transport{msg: "", status: http.StatusOK}})
	command := AddKeyCommand{}
	err := command.Run(&context, client)
	c.Assert(err, IsNil)
	c.Assert(manager.Stdout.(*bytes.Buffer).String(), Equals, expected)
}

func (s *S) TestRemoveKey(c *C) {
	expected := "Key removed with success!\n"
	context := Context{[]string{}, []string{}, manager.Stdout, manager.Stderr}
	client := NewClient(&http.Client{Transport: &transport{msg: "", status: http.StatusOK}})
	command := RemoveKey{}
	err := command.Run(&context, client)
	c.Assert(err, IsNil)
	c.Assert(manager.Stdout.(*bytes.Buffer).String(), Equals, expected)
}

func (s *S) TestKey(c *C) {
	expect := map[string]interface{}{
		"add":    &AddKeyCommand{},
		"remove": &RemoveKey{},
	}
	command := Key{}
	c.Assert(command.Subcommands(), DeepEquals, expect)
}

func (s *S) TestLogout(c *C) {
	expected := "Successfully logout!\n"
	context := Context{[]string{}, []string{}, manager.Stdout, manager.Stderr}
	command := Logout{}
	err := command.Run(&context, nil)
	c.Assert(err, IsNil)
	c.Assert(manager.Stdout.(*bytes.Buffer).String(), Equals, expected)

	token, err := ReadToken()
	c.Assert(token, Equals, "")
}

func (s *S) TestTeam(c *C) {
	expect := map[string]interface{}{
		"add-user":    &TeamAddUser{},
		"remove-user": &TeamRemoveUser{},
		"create":      &TeamCreate{},
	}
	command := Team{}
	c.Assert(command.Subcommands(), DeepEquals, expect)
}

func (s *S) TestTeamAddUser(c *C) {
	expected := `User "andorito" was added to the "cobrateam" team` + "\n"
	context := Context{[]string{}, []string{"cobrateam", "andorito"}, manager.Stdout, manager.Stderr}
	command := TeamAddUser{}
	client := NewClient(&http.Client{Transport: &transport{msg: "", status: http.StatusOK}})
	err := command.Run(&context, client)
	c.Assert(err, IsNil)
	c.Assert(manager.Stdout.(*bytes.Buffer).String(), Equals, expected)
}

func (s *S) TestTeamRemoveUser(c *C) {
	expected := `User "andorito" was removed from the "cobrateam" team` + "\n"
	context := Context{[]string{}, []string{"cobrateam", "andorito"}, manager.Stdout, manager.Stderr}
	command := TeamRemoveUser{}
	client := NewClient(&http.Client{Transport: &transport{msg: "", status: http.StatusOK}})
	err := command.Run(&context, client)
	c.Assert(err, IsNil)
	c.Assert(manager.Stdout.(*bytes.Buffer).String(), Equals, expected)
}

func (s *S) TestTeamCreate(c *C) {
	expected := `Team "core" created with success!` + "\n"
	context := Context{[]string{}, []string{"core"}, manager.Stdout, manager.Stderr}
	client := NewClient(&http.Client{Transport: &transport{msg: "", status: http.StatusCreated}})
	command := TeamCreate{}
	err := command.Run(&context, client)
	c.Assert(err, IsNil)
	c.Assert(manager.Stdout.(*bytes.Buffer).String(), Equals, expected)
}

func (s *S) TestUser(c *C) {
	expect := map[string]interface{}{
		"create": &UserCreate{},
	}
	command := User{}
	c.Assert(command.Subcommands(), DeepEquals, expect)
}

func (s *S) TestUserCreate(c *C) {
	patchStdin(c, []byte("bar123\n"))
	defer unpathStdin()
	expected := "Password: \n" + `User "foo@foo.com" created with success!` + "\n"
	context := Context{[]string{}, []string{"foo@foo.com"}, manager.Stdout, manager.Stderr}
	client := NewClient(&http.Client{Transport: &transport{msg: "", status: http.StatusCreated}})
	command := UserCreate{}
	err := command.Run(&context, client)
	c.Assert(err, IsNil)
	c.Assert(manager.Stdout.(*bytes.Buffer).String(), Equals, expected)
}

func (s *S) TestUserCreateShouldReturnErrorIfThePasswordIsNotGiven(c *C) {
	patchStdin(c, []byte("\n"))
	defer unpathStdin()
	expected := "Password: \nYou must provide the password!\n"
	context := Context{[]string{}, []string{"foo@foo.com"}, manager.Stdout, manager.Stderr}
	command := UserCreate{}
	err := command.Run(&context, nil)
	c.Assert(err, NotNil)
	c.Assert(manager.Stdout.(*bytes.Buffer).String(), Equals, expected)
}

func (s *S) TestGetPassword(c *C) {
	patchStdin(c, []byte("chico\n"))
	defer unpathStdin()
	pass := getPassword(os.Stdin.Fd())
	c.Assert(pass, Equals, "chico")
}

func (s *S) TestGetPasswordShouldRemoveAllNewLineCharactersFromTheEndOfThePassword(c *C) {
	patchStdin(c, []byte("chico\n\n\n"))
	defer unpathStdin()
	pass := getPassword(os.Stdin.Fd())
	c.Assert(pass, Equals, "chico")
}

func (s *S) TestGetPasswordShouldRemoveCarriageReturnCharacterFromTheEndOfThePassword(c *C) {
	patchStdin(c, []byte("opeth\r\n"))
	defer unpathStdin()
	pass := getPassword(os.Stdin.Fd())
	c.Assert(pass, Equals, "opeth")
}

func (s *S) TestGetPasswordWithEmptyPassword(c *C) {
	patchStdin(c, []byte("\n"))
	defer unpathStdin()
	pass := getPassword(os.Stdin.Fd())
	c.Assert(pass, Equals, "")
}
<commit_msg>cmd: added assertion for error<commit_after>package cmd

import (
	"bytes"
	. "launchpad.net/gocheck"
	"net/http"
	"os"
	"syscall"
)

func patchStdin(c *C, content []byte) {
	f, err := os.OpenFile("/tmp/passwdfile.txt", syscall.O_RDWR|syscall.O_NDELAY|syscall.O_CREAT|syscall.O_TRUNC, 0600)
	c.Assert(err, IsNil)
	n, err := f.Write(content)
	c.Assert(err, IsNil)
	c.Assert(n, Equals, len(content))
	ret, err := f.Seek(0, 0)
	c.Assert(err, IsNil)
	c.Assert(ret, Equals, int64(0))
	os.Stdin = f
}

func unpathStdin() {
	os.Stdin = os.NewFile(uintptr(syscall.Stdin), "/dev/stdin")
}

func (s *S) TestLogin(c *C) {
	patchStdin(c, []byte("chico\n"))
	defer unpathStdin()
	expected := "Password: \nSuccessfully logged!\n"
	context := Context{[]string{}, []string{"foo@foo.com"}, manager.Stdout, manager.Stderr}
	client := NewClient(&http.Client{Transport: &transport{msg: `{"token": "sometoken"}`, status: http.StatusOK}})
	command := Login{}
	err := command.Run(&context, client)
	c.Assert(err, IsNil)
	c.Assert(manager.Stdout.(*bytes.Buffer).String(), Equals, expected)

	token, err := ReadToken()
	c.Assert(err, IsNil)
	c.Assert(token, Equals, "sometoken")
}

func (s *S) TestLoginShouldReturnErrorIfThePasswordIsNotGiven(c *C) {
	patchStdin(c, []byte("\n"))
	defer unpathStdin()
	expected := "Password: \nYou must provide the password!\n"
	context := Context{[]string{}, []string{"foo@foo.com"}, manager.Stdout, manager.Stderr}
	command := Login{}
	err := command.Run(&context, nil)
	c.Assert(err, NotNil)
	c.Assert(manager.Stdout.(*bytes.Buffer).String(), Equals, expected)
}

func (s *S) TestAddKey(c *C) {
	expected := "Key added with success!\n"
	context := Context{[]string{}, []string{}, manager.Stdout, manager.Stderr}
	client := NewClient(&http.Client{Transport: &transport{msg: "", status: http.StatusOK}})
	command := AddKeyCommand{}
	err := command.Run(&context, client)
	c.Assert(err, IsNil)
	c.Assert(manager.Stdout.(*bytes.Buffer).String(), Equals, expected)
}

func (s *S) TestRemoveKey(c *C) {
	expected := "Key removed with success!\n"
	context := Context{[]string{}, []string{}, manager.Stdout, manager.Stderr}
	client := NewClient(&http.Client{Transport: &transport{msg: "", status: http.StatusOK}})
	command := RemoveKey{}
	err := command.Run(&context, client)
	c.Assert(err, IsNil)
	c.Assert(manager.Stdout.(*bytes.Buffer).String(), Equals, expected)
}

func (s *S) TestKey(c *C) {
	expect := map[string]interface{}{
		"add":    &AddKeyCommand{},
		"remove": &RemoveKey{},
	}
	command := Key{}
	c.Assert(command.Subcommands(), DeepEquals, expect)
}

func (s *S) TestLogout(c *C) {
	expected := "Successfully logout!\n"
	context := Context{[]string{}, []string{}, manager.Stdout, manager.Stderr}
	command := Logout{}
	err := command.Run(&context, nil)
	c.Assert(err, IsNil)
	c.Assert(manager.Stdout.(*bytes.Buffer).String(), Equals, expected)

	token, err := ReadToken()
	c.Assert(token, Equals, "")
}

func (s *S) TestTeam(c *C) {
	expect := map[string]interface{}{
		"add-user":    &TeamAddUser{},
		"remove-user": &TeamRemoveUser{},
		"create":      &TeamCreate{},
	}
	command := Team{}
	c.Assert(command.Subcommands(), DeepEquals, expect)
}

func (s *S) TestTeamAddUser(c *C) {
	expected := `User "andorito" was added to the "cobrateam" team` + "\n"
	context := Context{[]string{}, []string{"cobrateam", "andorito"}, manager.Stdout, manager.Stderr}
	command := TeamAddUser{}
	client := NewClient(&http.Client{Transport: &transport{msg: "", status: http.StatusOK}})
	err := command.Run(&context, client)
	c.Assert(err, IsNil)
	c.Assert(manager.Stdout.(*bytes.Buffer).String(), Equals, expected)
}

func (s *S) TestTeamRemoveUser(c *C) {
	expected := `User "andorito" was removed from the "cobrateam" team` + "\n"
	context := Context{[]string{}, []string{"cobrateam", "andorito"}, manager.Stdout, manager.Stderr}
	command := TeamRemoveUser{}
	client := NewClient(&http.Client{Transport: &transport{msg: "", status: http.StatusOK}})
	err := command.Run(&context, client)
	c.Assert(err, IsNil)
	c.Assert(manager.Stdout.(*bytes.Buffer).String(), Equals, expected)
}

func (s *S) TestTeamCreate(c *C) {
	expected := `Team "core" created with success!` + "\n"
	context := Context{[]string{}, []string{"core"}, manager.Stdout, manager.Stderr}
	client := NewClient(&http.Client{Transport: &transport{msg: "", status: http.StatusCreated}})
	command := TeamCreate{}
	err := command.Run(&context, client)
	c.Assert(err, IsNil)
	c.Assert(manager.Stdout.(*bytes.Buffer).String(), Equals, expected)
}

func (s *S) TestUser(c *C) {
	expect := map[string]interface{}{
		"create": &UserCreate{},
	}
	command := User{}
	c.Assert(command.Subcommands(), DeepEquals, expect)
}

func (s *S) TestUserCreate(c *C) {
	patchStdin(c, []byte("bar123\n"))
	defer unpathStdin()
	expected := "Password: \n" + `User "foo@foo.com" created with success!` + "\n"
	context := Context{[]string{}, []string{"foo@foo.com"}, manager.Stdout, manager.Stderr}
	client := NewClient(&http.Client{Transport: &transport{msg: "", status: http.StatusCreated}})
	command := UserCreate{}
	err := command.Run(&context, client)
	c.Assert(err, IsNil)
	c.Assert(manager.Stdout.(*bytes.Buffer).String(), Equals, expected)
}

func (s *S) TestUserCreateShouldReturnErrorIfThePasswordIsNotGiven(c *C) {
	patchStdin(c, []byte("\n"))
	defer unpathStdin()
	expected := "Password: \nYou must provide the password!\n"
	context := Context{[]string{}, []string{"foo@foo.com"}, manager.Stdout, manager.Stderr}
	command := UserCreate{}
	err := command.Run(&context, nil)
	c.Assert(err, NotNil)
	c.Assert(manager.Stdout.(*bytes.Buffer).String(), Equals, expected)
}

func (s *S) TestGetPassword(c *C) {
	patchStdin(c, []byte("chico\n"))
	defer unpathStdin()
	pass := getPassword(os.Stdin.Fd())
	c.Assert(pass, Equals, "chico")
}

func (s *S) TestGetPasswordShouldRemoveAllNewLineCharactersFromTheEndOfThePassword(c *C) {
	patchStdin(c, []byte("chico\n\n\n"))
	defer unpathStdin()
	pass := getPassword(os.Stdin.Fd())
	c.Assert(pass, Equals, "chico")
}

func (s *S) TestGetPasswordShouldRemoveCarriageReturnCharacterFromTheEndOfThePassword(c *C) {
	patchStdin(c, []byte("opeth\r\n"))
	defer unpathStdin()
	pass := getPassword(os.Stdin.Fd())
	c.Assert(pass, Equals, "opeth")
}

func (s *S) TestGetPasswordWithEmptyPassword(c *C) {
	patchStdin(c, []byte("\n"))
	defer unpathStdin()
	pass := getPassword(os.Stdin.Fd())
	c.Assert(pass, Equals, "")
}
<|endoftext|>