<commit_before>package commands

import (
	"context"
	"fmt"
	"io"
	"os"

	oldCmds "github.com/ipfs/go-ipfs/commands"
	lgc "github.com/ipfs/go-ipfs/commands/legacy"
	"github.com/ipfs/go-ipfs/core"
	cmdenv "github.com/ipfs/go-ipfs/core/commands/cmdenv"
	e "github.com/ipfs/go-ipfs/core/commands/e"
	"github.com/ipfs/go-ipfs/filestore"

	cmds "gx/ipfs/QmPTfgFTo9PFr1PvPKyKoeMgBvYPh6cX3aDP7DHKVbnCbi/go-ipfs-cmds"
	"gx/ipfs/QmSP88ryZkHSRn1fnngAaV2Vcn63WUJzAavnRM9CVdU1Ky/go-ipfs-cmdkit"
	cid "gx/ipfs/QmZFbDTY9jfSBms2MchvYM9oYRbAF19K7Pby47yDBfpPrb/go-cid"
)

var FileStoreCmd = &cmds.Command{
	Helptext: cmdkit.HelpText{
		Tagline: "Interact with filestore objects.",
	},
	Subcommands: map[string]*cmds.Command{
		"ls":     lsFileStore,
		"verify": lgc.NewCommand(verifyFileStore),
		"dups":   lgc.NewCommand(dupsFileStore),
	},
}

var lsFileStore = &cmds.Command{
	Helptext: cmdkit.HelpText{
		Tagline: "List objects in filestore.",
		LongDescription: `
List objects in the filestore.

If one or more <obj> is specified only list those specific objects,
otherwise list all objects.

The output is:

<hash> <size> <path> <offset>
`,
	},
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("obj", false, true, "Cid of objects to list."),
	},
	Options: []cmdkit.Option{
		cmdkit.BoolOption("file-order", "sort the results based on the path of the backing file"),
	},
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) {
		_, fs, err := getFilestore(env)
		if err != nil {
			res.SetError(err, cmdkit.ErrNormal)
			return
		}
		args := req.Arguments
		if len(args) > 0 {
			out := perKeyActionToChan(req.Context, args, func(c *cid.Cid) *filestore.ListRes {
				return filestore.List(fs, c)
			})

			err = res.Emit(out)
			if err != nil {
				log.Error(err)
			}
		} else {
			fileOrder, _ := req.Options["file-order"].(bool)
			next, err := filestore.ListAll(fs, fileOrder)
			if err != nil {
				res.SetError(err, cmdkit.ErrNormal)
				return
			}

			out := listResToChan(req.Context, next)
			err = res.Emit(out)
			if err != nil {
				log.Error(err)
			}
		}
	},
	PostRun: cmds.PostRunMap{
		cmds.CLI: func(req *cmds.Request, re cmds.ResponseEmitter) cmds.ResponseEmitter {
			reNext, res := cmds.NewChanResponsePair(req)

			go func() {
				defer re.Close()

				var errors bool
				for {
					v, err := res.Next()
					if !cmds.HandleError(err, res, re) {
						break
					}

					r, ok := v.(*filestore.ListRes)
					if !ok {
						log.Error(e.New(e.TypeErr(r, v)))
						return
					}

					if r.ErrorMsg != "" {
						errors = true
						fmt.Fprintf(os.Stderr, "%s\n", r.ErrorMsg)
					} else {
						fmt.Fprintf(os.Stdout, "%s\n", r.FormatLong())
					}
				}

				if errors {
					re.SetError("errors while displaying some entries", cmdkit.ErrNormal)
				}
			}()

			return reNext
		},
	},
	Type: filestore.ListRes{},
}

var verifyFileStore = &oldCmds.Command{
	Helptext: cmdkit.HelpText{
		Tagline: "Verify objects in filestore.",
		LongDescription: `
Verify objects in the filestore.

If one or more <obj> is specified only verify those specific objects,
otherwise verify all objects.

The output is:

<status> <hash> <size> <path> <offset>

Where <status> is one of:
ok:       the block can be reconstructed
changed:  the contents of the backing file have changed
no-file:  the backing file could not be found
error:    there was some other problem reading the file
missing:  <obj> could not be found in the filestore
ERROR:    internal error, most likely due to a corrupt database

For ERROR entries the error will also be printed to stderr.
`,
	},
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("obj", false, true, "Cid of objects to verify."),
	},
	Options: []cmdkit.Option{
		cmdkit.BoolOption("file-order", "verify the objects based on the order of the backing file"),
	},
	Run: func(req oldCmds.Request, res oldCmds.Response) {
		_, fs, err := getFilestore(req.InvocContext())
		if err != nil {
			res.SetError(err, cmdkit.ErrNormal)
			return
		}
		args := req.Arguments()
		if len(args) > 0 {
			out := perKeyActionToChan(req.Context(), args, func(c *cid.Cid) *filestore.ListRes {
				return filestore.Verify(fs, c)
			})
			res.SetOutput(out)
		} else {
			fileOrder, _, _ := req.Option("file-order").Bool()
			next, err := filestore.VerifyAll(fs, fileOrder)
			if err != nil {
				res.SetError(err, cmdkit.ErrNormal)
				return
			}
			out := listResToChan(req.Context(), next)
			res.SetOutput(out)
		}
	},
	Marshalers: oldCmds.MarshalerMap{
		oldCmds.Text: func(res oldCmds.Response) (io.Reader, error) {
			v, err := unwrapOutput(res.Output())
			if err != nil {
				return nil, err
			}

			r, ok := v.(*filestore.ListRes)
			if !ok {
				return nil, e.TypeErr(r, v)
			}

			if r.Status == filestore.StatusOtherError {
				fmt.Fprintf(res.Stderr(), "%s\n", r.ErrorMsg)
			}
			fmt.Fprintf(res.Stdout(), "%s %s\n", r.Status.Format(), r.FormatLong())
			return nil, nil
		},
	},
	Type: filestore.ListRes{},
}

var dupsFileStore = &oldCmds.Command{
	Helptext: cmdkit.HelpText{
		Tagline: "List blocks that are both in the filestore and standard block storage.",
	},
	Run: func(req oldCmds.Request, res oldCmds.Response) {
		_, fs, err := getFilestore(req.InvocContext())
		if err != nil {
			res.SetError(err, cmdkit.ErrNormal)
			return
		}
		ch, err := fs.FileManager().AllKeysChan(req.Context())
		if err != nil {
			res.SetError(err, cmdkit.ErrNormal)
			return
		}

		out := make(chan interface{}, 128)
		res.SetOutput((<-chan interface{})(out))

		go func() {
			defer close(out)
			for cid := range ch {
				have, err := fs.MainBlockstore().Has(cid)
				if err != nil {
					out <- &RefWrapper{Err: err.Error()}
					return
				}
				if have {
					out <- &RefWrapper{Ref: cid.String()}
				}
			}
		}()
	},
	Marshalers: refsMarshallerMap,
	Type:       RefWrapper{},
}

func getFilestore(env interface{}) (*core.IpfsNode, *filestore.Filestore, error) {
	n, err := cmdenv.GetNode(env)
	if err != nil {
		return nil, nil, err
	}
	fs := n.Filestore
	if fs == nil {
		return n, nil, filestore.ErrFilestoreNotEnabled
	}
	return n, fs, err
}

func listResToChan(ctx context.Context, next func() *filestore.ListRes) <-chan interface{} {
	out := make(chan interface{}, 128)
	go func() {
		defer close(out)
		for {
			r := next()
			if r == nil {
				return
			}
			select {
			case out <- r:
			case <-ctx.Done():
				return
			}
		}
	}()
	return out
}

func perKeyActionToChan(ctx context.Context, args []string, action func(*cid.Cid) *filestore.ListRes) <-chan interface{} {
	out := make(chan interface{}, 128)
	go func() {
		defer close(out)
		for _, arg := range args {
			c, err := cid.Decode(arg)
			if err != nil {
				out <- &filestore.ListRes{
					Status:   filestore.StatusOtherError,
					ErrorMsg: fmt.Sprintf("%s: %v", arg, err),
				}
				continue
			}
			r := action(c)
			select {
			case out <- r:
			case <-ctx.Done():
				return
			}
		}
	}()
	return out
}
<commit_msg>fix goroutine leaks in filestore.go<commit_after>package commands

import (
	"context"
	"fmt"
	"io"
	"os"

	oldCmds "github.com/ipfs/go-ipfs/commands"
	lgc "github.com/ipfs/go-ipfs/commands/legacy"
	"github.com/ipfs/go-ipfs/core"
	cmdenv "github.com/ipfs/go-ipfs/core/commands/cmdenv"
	e "github.com/ipfs/go-ipfs/core/commands/e"
	"github.com/ipfs/go-ipfs/filestore"

	cmds "gx/ipfs/QmPTfgFTo9PFr1PvPKyKoeMgBvYPh6cX3aDP7DHKVbnCbi/go-ipfs-cmds"
	"gx/ipfs/QmSP88ryZkHSRn1fnngAaV2Vcn63WUJzAavnRM9CVdU1Ky/go-ipfs-cmdkit"
	cid "gx/ipfs/QmZFbDTY9jfSBms2MchvYM9oYRbAF19K7Pby47yDBfpPrb/go-cid"
)

var FileStoreCmd = &cmds.Command{
	Helptext: cmdkit.HelpText{
		Tagline: "Interact with filestore objects.",
	},
	Subcommands: map[string]*cmds.Command{
		"ls":     lsFileStore,
		"verify": lgc.NewCommand(verifyFileStore),
		"dups":   lgc.NewCommand(dupsFileStore),
	},
}

var lsFileStore = &cmds.Command{
	Helptext: cmdkit.HelpText{
		Tagline: "List objects in filestore.",
		LongDescription: `
List objects in the filestore.

If one or more <obj> is specified only list those specific objects,
otherwise list all objects.

The output is:

<hash> <size> <path> <offset>
`,
	},
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("obj", false, true, "Cid of objects to list."),
	},
	Options: []cmdkit.Option{
		cmdkit.BoolOption("file-order", "sort the results based on the path of the backing file"),
	},
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) {
		_, fs, err := getFilestore(env)
		if err != nil {
			res.SetError(err, cmdkit.ErrNormal)
			return
		}
		args := req.Arguments
		if len(args) > 0 {
			out := perKeyActionToChan(req.Context, args, func(c *cid.Cid) *filestore.ListRes {
				return filestore.List(fs, c)
			})

			err = res.Emit(out)
			if err != nil {
				log.Error(err)
			}
		} else {
			fileOrder, _ := req.Options["file-order"].(bool)
			next, err := filestore.ListAll(fs, fileOrder)
			if err != nil {
				res.SetError(err, cmdkit.ErrNormal)
				return
			}

			out := listResToChan(req.Context, next)
			err = res.Emit(out)
			if err != nil {
				log.Error(err)
			}
		}
	},
	PostRun: cmds.PostRunMap{
		cmds.CLI: func(req *cmds.Request, re cmds.ResponseEmitter) cmds.ResponseEmitter {
			reNext, res := cmds.NewChanResponsePair(req)

			go func() {
				defer re.Close()

				var errors bool
				for {
					v, err := res.Next()
					if !cmds.HandleError(err, res, re) {
						break
					}

					r, ok := v.(*filestore.ListRes)
					if !ok {
						log.Error(e.New(e.TypeErr(r, v)))
						return
					}

					if r.ErrorMsg != "" {
						errors = true
						fmt.Fprintf(os.Stderr, "%s\n", r.ErrorMsg)
					} else {
						fmt.Fprintf(os.Stdout, "%s\n", r.FormatLong())
					}
				}

				if errors {
					re.SetError("errors while displaying some entries", cmdkit.ErrNormal)
				}
			}()

			return reNext
		},
	},
	Type: filestore.ListRes{},
}

var verifyFileStore = &oldCmds.Command{
	Helptext: cmdkit.HelpText{
		Tagline: "Verify objects in filestore.",
		LongDescription: `
Verify objects in the filestore.

If one or more <obj> is specified only verify those specific objects,
otherwise verify all objects.

The output is:

<status> <hash> <size> <path> <offset>

Where <status> is one of:
ok:       the block can be reconstructed
changed:  the contents of the backing file have changed
no-file:  the backing file could not be found
error:    there was some other problem reading the file
missing:  <obj> could not be found in the filestore
ERROR:    internal error, most likely due to a corrupt database

For ERROR entries the error will also be printed to stderr.
`,
	},
	Arguments: []cmdkit.Argument{
		cmdkit.StringArg("obj", false, true, "Cid of objects to verify."),
	},
	Options: []cmdkit.Option{
		cmdkit.BoolOption("file-order", "verify the objects based on the order of the backing file"),
	},
	Run: func(req oldCmds.Request, res oldCmds.Response) {
		_, fs, err := getFilestore(req.InvocContext())
		if err != nil {
			res.SetError(err, cmdkit.ErrNormal)
			return
		}
		args := req.Arguments()
		if len(args) > 0 {
			out := perKeyActionToChan(req.Context(), args, func(c *cid.Cid) *filestore.ListRes {
				return filestore.Verify(fs, c)
			})
			res.SetOutput(out)
		} else {
			fileOrder, _, _ := req.Option("file-order").Bool()
			next, err := filestore.VerifyAll(fs, fileOrder)
			if err != nil {
				res.SetError(err, cmdkit.ErrNormal)
				return
			}
			out := listResToChan(req.Context(), next)
			res.SetOutput(out)
		}
	},
	Marshalers: oldCmds.MarshalerMap{
		oldCmds.Text: func(res oldCmds.Response) (io.Reader, error) {
			v, err := unwrapOutput(res.Output())
			if err != nil {
				return nil, err
			}

			r, ok := v.(*filestore.ListRes)
			if !ok {
				return nil, e.TypeErr(r, v)
			}

			if r.Status == filestore.StatusOtherError {
				fmt.Fprintf(res.Stderr(), "%s\n", r.ErrorMsg)
			}
			fmt.Fprintf(res.Stdout(), "%s %s\n", r.Status.Format(), r.FormatLong())
			return nil, nil
		},
	},
	Type: filestore.ListRes{},
}

var dupsFileStore = &oldCmds.Command{
	Helptext: cmdkit.HelpText{
		Tagline: "List blocks that are both in the filestore and standard block storage.",
	},
	Run: func(req oldCmds.Request, res oldCmds.Response) {
		_, fs, err := getFilestore(req.InvocContext())
		if err != nil {
			res.SetError(err, cmdkit.ErrNormal)
			return
		}
		ch, err := fs.FileManager().AllKeysChan(req.Context())
		if err != nil {
			res.SetError(err, cmdkit.ErrNormal)
			return
		}

		out := make(chan interface{}, 128)
		res.SetOutput((<-chan interface{})(out))

		go func() {
			defer close(out)
			for cid := range ch {
				have, err := fs.MainBlockstore().Has(cid)
				if err != nil {
					out <- &RefWrapper{Err: err.Error()}
					return
				}
				if have {
					out <- &RefWrapper{Ref: cid.String()}
				}
			}
		}()
	},
	Marshalers: refsMarshallerMap,
	Type:       RefWrapper{},
}

func getFilestore(env interface{}) (*core.IpfsNode, *filestore.Filestore, error) {
	n, err := cmdenv.GetNode(env)
	if err != nil {
		return nil, nil, err
	}
	fs := n.Filestore
	if fs == nil {
		return n, nil, filestore.ErrFilestoreNotEnabled
	}
	return n, fs, err
}

func listResToChan(ctx context.Context, next func() *filestore.ListRes) <-chan interface{} {
	out := make(chan interface{}, 128)
	go func() {
		defer close(out)
		for {
			r := next()
			if r == nil {
				return
			}
			select {
			case out <- r:
			case <-ctx.Done():
				return
			}
		}
	}()
	return out
}

func perKeyActionToChan(ctx context.Context, args []string, action func(*cid.Cid) *filestore.ListRes) <-chan interface{} {
	out := make(chan interface{}, 128)
	go func() {
		defer close(out)
		for _, arg := range args {
			c, err := cid.Decode(arg)
			if err != nil {
				select {
				case out <- &filestore.ListRes{
					Status:   filestore.StatusOtherError,
					ErrorMsg: fmt.Sprintf("%s: %v", arg, err),
				}:
				case <-ctx.Done():
				}

				continue
			}
			r := action(c)
			select {
			case out <- r:
			case <-ctx.Done():
				return
			}
		}
	}()
	return out
}
<|endoftext|>


<commit_before>package commands

import (
	"errors"
	"fmt"
	"io"

	//ds "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/ipfs/go-datastore"
	bs "github.com/ipfs/go-ipfs/blocks/blockstore"
	k "github.com/ipfs/go-ipfs/blocks/key"
	cmds "github.com/ipfs/go-ipfs/commands"
	"github.com/ipfs/go-ipfs/core"
	"github.com/ipfs/go-ipfs/filestore"
	"github.com/ipfs/go-ipfs/repo/fsrepo"
	b58 "gx/ipfs/QmT8rehPR3F6bmwL6zjUN8XpiDBFFpMP2myPdC6ApsWfJf/go-base58"
	context "gx/ipfs/QmZy2y8t9zQH2a1b8q2ZSLKp17ATuJoCNxxyMFG5qFExpt/go-net/context"
)

var FileStoreCmd = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline: "Interact with filestore objects",
	},
	Subcommands: map[string]*cmds.Command{
		"ls":         lsFileStore,
		"verify":     verifyFileStore,
		"rm":         rmFilestoreObjs,
		"rm-invalid": rmInvalidObjs,
		//"rm-incomplete":      rmIncompleteObjs,
		"find-dangling-pins": findDanglingPins,
	},
}

var lsFileStore = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline: "List objects in filestore",
		ShortDescription: `
List objects in the filestore.  If --quiet is specified only the
hashes are printed, otherwise the fields are as follows:
  <hash> <type> <filepath> <offset> <size>
where <type> is one of"
  leaf: to indicate a leaf node where the contents are stored
        to in the file itself
  root: to indicate a root node that represents the whole file
  other: some other kind of node that represent part of a file
and <filepath> is the part of the file the object represents.  The
part represented starts at <offset> and continues for <size> bytes.
If <offset> is the special value "-" than the "leaf" or "root" node
represents the whole file.
`,
	},
	Options: []cmds.Option{
		cmds.BoolOption("quiet", "q", "Write just hashes of objects."),
	},
	Run: func(req cmds.Request, res cmds.Response) {
		_, fs, err := extractFilestore(req)
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
		quiet, _, err := res.Request().Option("quiet").Bool()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
		ch, _ := filestore.List(fs, quiet)
		res.SetOutput(&chanWriter{ch, "", 0})
	},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
			return res.(io.Reader), nil
		},
	},
}

type chanWriter struct {
	ch     <-chan filestore.ListRes
	buf    string
	offset int
}

func (w *chanWriter) Read(p []byte) (int, error) {
	if w.offset >= len(w.buf) {
		w.offset = 0
		res, more := <-w.ch
		if !more {
			return 0, io.EOF
		}
		if res.DataObj == nil {
			w.buf = res.MHash() + "\n"
		} else {
			w.buf = res.Format()
		}
	}
	sz := copy(p, w.buf[w.offset:])
	w.offset += sz
	return sz, nil
}

var verifyFileStore = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline: "Verify objects in filestore",
		ShortDescription: `
Verify leaf nodes in the filestore, the output is:
  <status> <type> <filepath> <offset> <size>
where <type>, <filepath>, <offset> and <size> are the same as in the
"ls" command and <status> is one of:
  ok:      If the object is okay
  changed: If the object is invalid becuase the contents of the file
           have changed
  missing: If the file can not be found
  error:   If the file can be found but could not be read or some
           other error
`,
	},
	Run: func(req cmds.Request, res cmds.Response) {
		_, fs, err := extractFilestore(req)
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
		ch,_ := filestore.List(fs, false)
		rdr, wtr := io.Pipe()
		go func() {
			defer wtr.Close()
			for res := range ch {
				if !res.NoBlockData {
					continue
				}
				res.Status = filestore.Verify(fs, res.Key, res.DataObj)
				wtr.Write([]byte(res.Format()))
			}
		}()
		res.SetOutput(rdr)
	},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
			return res.(io.Reader), nil
		},
	},
}

var rmFilestoreObjs = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline: "Remove objects from the filestore",
	},
	Arguments: []cmds.Argument{
		cmds.StringArg("hash", true, true, "Multi-hashes to remove."),
	},
	Run: func(req cmds.Request, res cmds.Response) {
		node, fs, err := extractFilestore(req)
		_ = fs
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
		hashes := req.Arguments()
		serr := res.Stderr()
		numErrors := 0
		for _, mhash := range hashes {
			key := k.B58KeyDecode(mhash)
			err = delFilestoreObj(req, node, fs, key)
			if err != nil {
				fmt.Fprintf(serr, "Error deleting %s: %s\n", mhash, err.Error())
				numErrors += 1
			}
		}
		if numErrors > 0 {
			res.SetError(errors.New("Could not delete some keys"), cmds.ErrNormal)
			return
		}
		return
	},
}

var rmInvalidObjs = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline: "Remove invalid objects from the filestore",
		ShortDescription: `
Removes objects that have become invalid from the Filestrore up to the
reason specified in <level>.  If <level> is "changed" than remove any
blocks that have become invalid due to the contents of the underlying
file changing.  If <level> is "missing" also remove any blocks that
have become invalid because the underlying file is no longer available
due to a "No such file" or related error, but not if the file exists
but is unreadable for some reason.  If <level> is "all" remove any
blocks that fail to validate regardless of the reason.
`,
	},

	Arguments: []cmds.Argument{
		cmds.StringArg("level", true, false, "one of changed, missing. or all").EnableStdin(),
	},
	Options: []cmds.Option{
		cmds.BoolOption("quiet", "q", "Produce less output."),
		cmds.BoolOption("dry-run", "n", "Do everything except the actual delete."),
	},
	Run: func(req cmds.Request, res cmds.Response) {
		node, fs, err := extractFilestore(req)
		_ = fs
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
		args := req.Arguments()
		if len(args) != 1 {
			res.SetError(errors.New("invalid usage"), cmds.ErrNormal)
			return
		}
		mode := req.Arguments()[0]
		level := filestore.StatusMissing
		switch mode {
		case "changed":
			level = filestore.StatusChanged
		case "missing":
			level = filestore.StatusMissing
		case "all":
			level = filestore.StatusError
		default:
			res.SetError(errors.New("level must be one of: changed missing all"), cmds.ErrNormal)
		}
		quiet, _, err := res.Request().Option("quiet").Bool()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
		dryRun, _, err := res.Request().Option("dry-run").Bool()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
		ch,_ := filestore.List(fs, false)
		rdr, wtr := io.Pipe()
		go func() {
			defer wtr.Close()
			var toDel [][]byte
			for r := range ch {
				if !r.NoBlockData {
					continue
				}
				r.Status = filestore.Verify(fs, r.Key, r.DataObj)
				if r.Status >= level {
					toDel = append(toDel, r.RawHash())
					if !quiet {
						fmt.Fprintf(wtr, "will delete %s (part of %s)\n", r.MHash(), r.FilePath)
					}
				}
			}
			if dryRun {
				fmt.Fprintf(wtr, "Dry-run option specified.  Stopping.\n")
				fmt.Fprintf(wtr, "Would of deleted %d invalid objects.\n", len(toDel))
			} else {
				for _, key := range toDel {
					err = delFilestoreObj(req, node, fs, k.Key(key))
					if err != nil {
						mhash := b58.Encode(key)
						msg := fmt.Sprintf("Could not delete %s: %s\n", mhash, err.Error())
						res.SetError(errors.New(msg), cmds.ErrNormal)
						return

					}
				}
				fmt.Fprintf(wtr, "Deleted %d invalid objects.\n", len(toDel))
			}
		}()
		res.SetOutput(rdr)
		return
	},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
			return res.(io.Reader), nil
		},
	},
}

func delFilestoreObj(req cmds.Request, node *core.IpfsNode, fs *filestore.Datastore, key k.Key) error {
	err := fs.DeleteDirect(key.DsKey())
	if err != nil {
		return err
	}
	stillExists, err := node.Blockstore.Has(key)
	if err != nil {
		return err
	}
	if stillExists {
		return nil
	}
	_, pinned1, err := node.Pinning.IsPinnedWithType(key, "recursive")
	if err != nil {
		return err
	}
	_, pinned2, err := node.Pinning.IsPinnedWithType(key, "direct")
	if err != nil {
		return err
	}
	if pinned1 || pinned2 {
		println("unpinning")
		ctx, cancel := context.WithCancel(req.Context())
		defer cancel()
		err = node.Pinning.Unpin(ctx, key, true)
		if err != nil {
			return err
		}
		err := node.Pinning.Flush()
		if err != nil {
			return err
		}
	}
	return nil
}

func extractFilestore(req cmds.Request) (node *core.IpfsNode, fs *filestore.Datastore, err error) {
	node, err = req.InvocContext().GetNode()
	if err != nil {
		return
	}
	repo, ok := node.Repo.Self().(*fsrepo.FSRepo)
	if !ok {
		err = errors.New("Not a FSRepo")
		return
	}
	fs = repo.Filestore()
	return
}

var findDanglingPins = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline: "List pinned objects that no longer exists",
	},
	Run: func(req cmds.Request, res cmds.Response) {
		n, err := req.InvocContext().GetNode()
		if err != nil {
			return
		}
		r, w := io.Pipe()
		go func() {
			defer w.Close()
			err := listDanglingPins(n.Pinning.DirectKeys(), w, n.Blockstore)
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
			}
			err = listDanglingPins(n.Pinning.RecursiveKeys(), w, n.Blockstore)
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
			}
		}()
		res.SetOutput(r)
	},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
			return res.(io.Reader), nil
		},
	},
}

func listDanglingPins(keys []k.Key, out io.Writer, d bs.Blockstore) error {
	for _, k := range keys {
		exists, err := d.Has(k)
		if err != nil {
			return err
		}
		if !exists {
			fmt.Fprintln(out, k.B58String())
		}
	}
	return nil
}
<commit_msg>Enhance "filestore rm" to give better output.<commit_after>package commands

import (
	"errors"
	"fmt"
	"io"
	"io/ioutil"

	//ds "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/ipfs/go-datastore"
	bs "github.com/ipfs/go-ipfs/blocks/blockstore"
	k "github.com/ipfs/go-ipfs/blocks/key"
	cmds "github.com/ipfs/go-ipfs/commands"
	"github.com/ipfs/go-ipfs/core"
	"github.com/ipfs/go-ipfs/filestore"
	"github.com/ipfs/go-ipfs/repo/fsrepo"
	b58 "gx/ipfs/QmT8rehPR3F6bmwL6zjUN8XpiDBFFpMP2myPdC6ApsWfJf/go-base58"
	context "gx/ipfs/QmZy2y8t9zQH2a1b8q2ZSLKp17ATuJoCNxxyMFG5qFExpt/go-net/context"
)

var FileStoreCmd = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline: "Interact with filestore objects",
	},
	Subcommands: map[string]*cmds.Command{
		"ls":         lsFileStore,
		"verify":     verifyFileStore,
		"rm":         rmFilestoreObjs,
		"rm-invalid": rmInvalidObjs,
		//"rm-incomplete":      rmIncompleteObjs,
		"find-dangling-pins": findDanglingPins,
	},
}

var lsFileStore = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline: "List objects in filestore",
		ShortDescription: `
List objects in the filestore.  If --quiet is specified only the
hashes are printed, otherwise the fields are as follows:
  <hash> <type> <filepath> <offset> <size>
where <type> is one of"
  leaf: to indicate a leaf node where the contents are stored
        to in the file itself
  root: to indicate a root node that represents the whole file
  other: some other kind of node that represent part of a file
and <filepath> is the part of the file the object represents.  The
part represented starts at <offset> and continues for <size> bytes.
If <offset> is the special value "-" than the "leaf" or "root" node
represents the whole file.
`,
	},
	Options: []cmds.Option{
		cmds.BoolOption("quiet", "q", "Write just hashes of objects."),
	},
	Run: func(req cmds.Request, res cmds.Response) {
		_, fs, err := extractFilestore(req)
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
		quiet, _, err := res.Request().Option("quiet").Bool()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
		ch, _ := filestore.List(fs, quiet)
		res.SetOutput(&chanWriter{ch, "", 0})
	},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
			return res.(io.Reader), nil
		},
	},
}

type chanWriter struct {
	ch     <-chan filestore.ListRes
	buf    string
	offset int
}

func (w *chanWriter) Read(p []byte) (int, error) {
	if w.offset >= len(w.buf) {
		w.offset = 0
		res, more := <-w.ch
		if !more {
			return 0, io.EOF
		}
		if res.DataObj == nil {
			w.buf = res.MHash() + "\n"
		} else {
			w.buf = res.Format()
		}
	}
	sz := copy(p, w.buf[w.offset:])
	w.offset += sz
	return sz, nil
}

var verifyFileStore = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline: "Verify objects in filestore",
		ShortDescription: `
Verify leaf nodes in the filestore, the output is:
  <status> <type> <filepath> <offset> <size>
where <type>, <filepath>, <offset> and <size> are the same as in the
"ls" command and <status> is one of:
  ok:      If the object is okay
  changed: If the object is invalid becuase the contents of the file
           have changed
  missing: If the file can not be found
  error:   If the file can be found but could not be read or some
           other error
`,
	},
	Run: func(req cmds.Request, res cmds.Response) {
		_, fs, err := extractFilestore(req)
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
		ch, _ := filestore.List(fs, false)
		rdr, wtr := io.Pipe()
		go func() {
			defer wtr.Close()
			for res := range ch {
				if !res.NoBlockData {
					continue
				}
				res.Status = filestore.Verify(fs, res.Key, res.DataObj)
				wtr.Write([]byte(res.Format()))
			}
		}()
		res.SetOutput(rdr)
	},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
			return res.(io.Reader), nil
		},
	},
}

var rmFilestoreObjs = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline: "Remove objects from the filestore",
	},
	Arguments: []cmds.Argument{
		cmds.StringArg("hash", true, true, "Multi-hashes to remove."),
	},
	Options: []cmds.Option{
		cmds.BoolOption("quiet", "q", "Produce less output."),
	},
	Run: func(req cmds.Request, res cmds.Response) {
		node, fs, err := extractFilestore(req)
		_ = fs
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
		quiet, _, err := res.Request().Option("quiet").Bool()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
		hashes := req.Arguments()
		rdr, wtr := io.Pipe()
		var rmWtr io.Writer = wtr
		if quiet {
			rmWtr = ioutil.Discard
		}
		go func() {
			numErrors := 0
			for _, mhash := range hashes {
				key := k.B58KeyDecode(mhash)
				err = delFilestoreObj(req, rmWtr, node, fs, key)
				if err != nil {
					fmt.Fprintf(wtr, "Error deleting %s: %s\n", mhash, err.Error())
					numErrors += 1
				}
			}
			if numErrors > 0 {
				wtr.CloseWithError(errors.New("Could not delete some keys."))
				return
			}
			wtr.Close()
		}()
		res.SetOutput(rdr)
		return
	},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
			return res.(io.Reader), nil
		},
	},
}

var rmInvalidObjs = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline: "Remove invalid objects from the filestore",
		ShortDescription: `
Removes objects that have become invalid from the Filestrore up to the
reason specified in <level>.  If <level> is "changed" than remove any
blocks that have become invalid due to the contents of the underlying
file changing.  If <level> is "missing" also remove any blocks that
have become invalid because the underlying file is no longer available
due to a "No such file" or related error, but not if the file exists
but is unreadable for some reason.  If <level> is "all" remove any
blocks that fail to validate regardless of the reason.
`,
	},

	Arguments: []cmds.Argument{
		cmds.StringArg("level", true, false, "one of changed, missing. or all").EnableStdin(),
	},
	Options: []cmds.Option{
		cmds.BoolOption("quiet", "q", "Produce less output."),
		cmds.BoolOption("dry-run", "n", "Do everything except the actual delete."),
	},
	Run: func(req cmds.Request, res cmds.Response) {
		node, fs, err := extractFilestore(req)
		_ = fs
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
		args := req.Arguments()
		if len(args) != 1 {
			res.SetError(errors.New("invalid usage"), cmds.ErrNormal)
			return
		}
		mode := req.Arguments()[0]
		level := filestore.StatusMissing
		switch mode {
		case "changed":
			level = filestore.StatusChanged
		case "missing":
			level = filestore.StatusMissing
		case "all":
			level = filestore.StatusError
		default:
			res.SetError(errors.New("level must be one of: changed missing all"), cmds.ErrNormal)
		}
		quiet, _, err := res.Request().Option("quiet").Bool()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
		dryRun, _, err := res.Request().Option("dry-run").Bool()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
		ch, _ := filestore.List(fs, false)
		rdr, wtr := io.Pipe()
		var rmWtr io.Writer = wtr
		if quiet {
			rmWtr = ioutil.Discard
		}
		go func() {
			var toDel [][]byte
			for r := range ch {
				if !r.NoBlockData {
					continue
				}
				r.Status = filestore.Verify(fs, r.Key, r.DataObj)
				if r.Status >= level {
					toDel = append(toDel, r.RawHash())
					if !quiet {
						fmt.Fprintf(wtr, "will delete %s (part of %s)\n", r.MHash(), r.FilePath)
					}
				}
			}
			if dryRun {
				fmt.Fprintf(wtr, "Dry-run option specified.  Stopping.\n")
				fmt.Fprintf(wtr, "Would of deleted %d invalid objects.\n", len(toDel))
			} else {
				for _, key := range toDel {
					err = delFilestoreObj(req, rmWtr, node, fs, k.Key(key))
					if err != nil {
						mhash := b58.Encode(key)
						msg := fmt.Sprintf("Could not delete %s: %s\n", mhash, err.Error())
						wtr.CloseWithError(errors.New(msg))
						return

					}
				}
				fmt.Fprintf(wtr, "Deleted %d invalid objects.\n", len(toDel))
			}
			wtr.Close()
		}()
		res.SetOutput(rdr)
		return
	},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
			return res.(io.Reader), nil
		},
	},
}

func delFilestoreObj(req cmds.Request, out io.Writer, node *core.IpfsNode, fs *filestore.Datastore, key k.Key) error {
	err := fs.DeleteDirect(key.DsKey())
	if err != nil {
		return err
	}
	stillExists, err := node.Blockstore.Has(key)
	if err != nil {
		return err
	}
	fmt.Fprintf(out, "Deleted %s\n", key)
	if stillExists {
		return nil
	}
	_, pinned1, err := node.Pinning.IsPinnedWithType(key, "recursive")
	if err != nil {
		return err
	}
	_, pinned2, err := node.Pinning.IsPinnedWithType(key, "direct")
	if err != nil {
		return err
	}
	if pinned1 || pinned2 {
		ctx, cancel := context.WithCancel(req.Context())
		defer cancel()
		err = node.Pinning.Unpin(ctx, key, true)
		if err != nil {
			return err
		}
		err := node.Pinning.Flush()
		if err != nil {
			return err
		}
		fmt.Fprintf(out, "Unpinned %s\n", key)
	}
	return nil
}

func extractFilestore(req cmds.Request) (node *core.IpfsNode, fs *filestore.Datastore, err error) {
	node, err = req.InvocContext().GetNode()
	if err != nil {
		return
	}
	repo, ok := node.Repo.Self().(*fsrepo.FSRepo)
	if !ok {
		err = errors.New("Not a FSRepo")
		return
	}
	fs = repo.Filestore()
	return
}

var findDanglingPins = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline: "List pinned objects that no longer exists",
	},
	Run: func(req cmds.Request, res cmds.Response) {
		n, err := req.InvocContext().GetNode()
		if err != nil {
			return
		}
		r, w := io.Pipe()
		go func() {
			defer w.Close()
			err := listDanglingPins(n.Pinning.DirectKeys(), w, n.Blockstore)
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
			}
			err = listDanglingPins(n.Pinning.RecursiveKeys(), w, n.Blockstore)
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
			}
		}()
		res.SetOutput(r)
	},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
			return res.(io.Reader), nil
		},
	},
}

func listDanglingPins(keys []k.Key, out io.Writer, d bs.Blockstore) error {
	for _, k := range keys {
		exists, err := d.Has(k)
		if err != nil {
			return err
		}
		if !exists {
			fmt.Fprintln(out, k.B58String())
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package imageprogress

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"regexp"
	"strings"
	"sync"
	"time"
)

const (
	defaultProgressTimeThreshhold = 30 * time.Second
	defaultStableThreshhold       = 10
)

// progressLine is a structure representation of a Docker pull progress line
type progressLine struct {
	ID     string          `json:"id"`
	Status string          `json:"status"`
	Detail *progressDetail `json:"progressDetail"`
	Error  string          `json:"error"`
}

// progressDetail is the progressDetail structure in a Docker pull progress line
type progressDetail struct {
	Current int64 `json:"current"`
	Total   int64 `json:"total"`
}

// layerDetail is layer information associated with a specific layerStatus
type layerDetail struct {
	Count   int
	Current int64
	Total   int64
}

// layerStatus is one of different possible status for layers detected by
// the ProgressWriter
type layerStatus int

const (
	statusPending layerStatus = iota
	statusDownloading
	statusExtracting
	statusComplete
	statusPushing
)

// layerStatusFromDockerString translates a string in a Docker status
// line to a layerStatus
func layerStatusFromDockerString(dockerStatus string) layerStatus {
	switch dockerStatus {
	case "Pushing":
		return statusPushing
	case "Downloading":
		return statusDownloading
	case "Extracting", "Verifying Checksum", "Download complete":
		return statusExtracting
	case "Pull complete", "Already exists", "Pushed":
		return statusComplete
	default:
		return statusPending
	}
}

type report map[layerStatus]*layerDetail

func (r report) count(status layerStatus) int {
	detail, ok := r[status]
	if !ok {
		return 0
	}
	return detail.Count
}

func (r report) percentProgress(status layerStatus) float32 {
	detail, ok := r[status]
	if !ok {
		return 0
	}
	if detail.Total == 0 {
		return 0
	}
	pct := float32(detail.Current) / float32(detail.Total) * 100.0
	if pct > 100.0 {
		pct = 100.0
	}
	return pct
}

func (r report) totalCount() int {
	cnt := 0
	for _, detail := range r {
		cnt += detail.Count
	}
	return cnt
}

// String is used for test output
func (r report) String() string {
	result := &bytes.Buffer{}
	fmt.Fprintf(result, "{")
	for k := range r {
		var status string
		switch k {
		case statusPending:
			status = "pending"
		case statusDownloading:
			status = "downloading"
		case statusExtracting:
			status = "extracting"
		case statusComplete:
			status = "complete"
		}
		fmt.Fprintf(result, "%s:{Count: %d, Current: %d, Total: %d}, ", status, r[k].Count, r[k].Current, r[k].Total)
	}
	fmt.Fprintf(result, "}")
	return result.String()
}

// newWriter creates a writer that periodically reports
// on pull/push progress of a Docker image. It only reports when the state of the
// different layers has changed and uses time thresholds to limit the
// rate of the reports.
func newWriter(reportFn func(report), layersChangedFn func(report, report) bool) io.Writer {
	writer := &imageProgressWriter{
		mutex:                  &sync.Mutex{},
		layerStatus:            map[string]progressLine{},
		reportFn:               reportFn,
		layersChangedFn:        layersChangedFn,
		progressTimeThreshhold: defaultProgressTimeThreshhold,
		stableThreshhold:       defaultStableThreshhold,
	}
	return writer
}

type imageProgressWriter struct {
	mutex                  *sync.Mutex
	internalWriter         io.Writer
	layerStatus            map[string]progressLine
	lastLayerCount         int
	stableLines            int
	stableThreshhold       int
	progressTimeThreshhold time.Duration
	lastReport             report
	lastReportTime         time.Time
	reportFn               func(report)
	layersChangedFn        func(report, report) bool
}

func (w *imageProgressWriter) ReadFrom(reader io.Reader) (int64, error) {
	decoder := json.NewDecoder(reader)
	return 0, w.readProgress(decoder)
}

func (w *imageProgressWriter) Write(data []byte) (int, error) {
	w.mutex.Lock()
	defer w.mutex.Unlock()
	if w.internalWriter == nil {
		var pipeIn *io.PipeReader
		pipeIn, w.internalWriter = io.Pipe()
		decoder := json.NewDecoder(pipeIn)
		go func() {
			err := w.readProgress(decoder)
			if err != nil {
				pipeIn.CloseWithError(err)
			}
		}()
	}
	return w.internalWriter.Write(data)
}

func (w *imageProgressWriter) readProgress(decoder *json.Decoder) error {
	for {
		line := &progressLine{}
		err := decoder.Decode(line)
		if err == io.EOF {
			break
		}
		if err != nil {
			return err
		}
		err = w.processLine(line)
		if err != nil {
			return err
		}
	}
	return nil
}

func (w *imageProgressWriter) processLine(line *progressLine) error {

	if err := getError(line); err != nil {
		return err
	}

	// determine if it's a line we want to process
	if !islayerStatus(line) {
		return nil
	}

	w.layerStatus[line.ID] = *line

	// if the number of layers has not stabilized yet, return and wait for more
	// progress
	if !w.isStableLayerCount() {
		return nil
	}

	r := createReport(w.layerStatus)

	// check if the count of layers in each state has changed
	if w.layersChangedFn(w.lastReport, r) {
		w.lastReport = r
		w.lastReportTime = time.Now()
		w.reportFn(r)
		return nil
	}
	// If layer counts haven't changed, but enough time has passed (30 sec by default),
	// at least report on download/push progress
	if time.Since(w.lastReportTime) > w.progressTimeThreshhold {
		w.lastReport = r
		w.lastReportTime = time.Now()
		w.reportFn(r)
	}
	return nil
}

func (w *imageProgressWriter) isStableLayerCount() bool {
	// If the number of layers has changed since last status, we're not stable
	if w.lastLayerCount != len(w.layerStatus) {
		w.lastLayerCount = len(w.layerStatus)
		w.stableLines = 0
		return false
	}
	// Only proceed after we've received status for the same number
	// of layers at least stableThreshhold times. If not, they're still increasing
	w.stableLines++
	if w.stableLines < w.stableThreshhold {
		// We're not stable enough yet
		return false
	}

	return true
}

var layerIDRegexp = regexp.MustCompile("^[a-f,0-9]*$")

func islayerStatus(line *progressLine) bool {
	// ignore status lines with no layer id
	if len(line.ID) == 0 {
		return false
	}
	// ignore layer ids that are not hex string
	if !layerIDRegexp.MatchString(line.ID) {
		return false
	}
	// ignore retrying status
	if strings.HasPrefix(line.Status, "Retrying") {
		return false
	}
	return true
}

func getError(line *progressLine) error {
	if len(line.Error) > 0 {
		return errors.New(line.Error)
	}
	return nil
}

func createReport(dockerProgress map[string]progressLine) report {
	r := report{}
	for _, line := range dockerProgress {
		layerStatus := layerStatusFromDockerString(line.Status)
		detail, exists := r[layerStatus]
		if !exists {
			detail = &layerDetail{}
			r[layerStatus] = detail
		}
		detail.Count++
		if line.Detail != nil {
			detail.Current += line.Detail.Current
			detail.Total += line.Detail.Total
		}
	}
	return r
}
<commit_msg>Image progress: recognize additional already pushed status<commit_after>package imageprogress

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"regexp"
	"strings"
	"sync"
	"time"
)

const (
	defaultProgressTimeThreshhold = 30 * time.Second
	defaultStableThreshhold       = 10
)

// progressLine is a structure representation of a Docker pull progress line
type progressLine struct {
	ID     string          `json:"id"`
	Status string          `json:"status"`
	Detail *progressDetail `json:"progressDetail"`
	Error  string          `json:"error"`
}

// progressDetail is the progressDetail structure in a Docker pull progress line
type progressDetail struct {
	Current int64 `json:"current"`
	Total   int64 `json:"total"`
}

// layerDetail is layer information associated with a specific layerStatus
type layerDetail struct {
	Count   int
	Current int64
	Total   int64
}

// layerStatus is one of different possible status for layers detected by
// the ProgressWriter
type layerStatus int

const (
	statusPending layerStatus = iota
	statusDownloading
	statusExtracting
	statusComplete
	statusPushing
)

// layerStatusFromDockerString translates a string in a Docker status
// line to a layerStatus
func layerStatusFromDockerString(dockerStatus string) layerStatus {
	switch dockerStatus {
	case "Pushing":
		return statusPushing
	case "Downloading":
		return statusDownloading
	case "Extracting", "Verifying Checksum", "Download complete":
		return statusExtracting
	case "Pull complete", "Already exists", "Pushed", "Layer already exists":
		return statusComplete
	default:
		return statusPending
	}
}

type report map[layerStatus]*layerDetail

func (r report) count(status layerStatus) int {
	detail, ok := r[status]
	if !ok {
		return 0
	}
	return detail.Count
}

func (r report) percentProgress(status layerStatus) float32 {
	detail, ok := r[status]
	if !ok {
		return 0
	}
	if detail.Total == 0 {
		return 0
	}
	pct := float32(detail.Current) / float32(detail.Total) * 100.0
	if pct > 100.0 {
		pct = 100.0
	}
	return pct
}

func (r report) totalCount() int {
	cnt := 0
	for _, detail := range r {
		cnt += detail.Count
	}
	return cnt
}

// String is used for test output
func (r report) String() string {
	result := &bytes.Buffer{}
	fmt.Fprintf(result, "{")
	for k := range r {
		var status string
		switch k {
		case statusPending:
			status = "pending"
		case statusDownloading:
			status = "downloading"
		case statusExtracting:
			status = "extracting"
		case statusComplete:
			status = "complete"
		}
		fmt.Fprintf(result, "%s:{Count: %d, Current: %d, Total: %d}, ", status, r[k].Count, r[k].Current, r[k].Total)
	}
	fmt.Fprintf(result, "}")
	return result.String()
}

// newWriter creates a writer that periodically reports
// on pull/push progress of a Docker image. It only reports when the state of the
// different layers has changed and uses time thresholds to limit the
// rate of the reports.
func newWriter(reportFn func(report), layersChangedFn func(report, report) bool) io.Writer {
	writer := &imageProgressWriter{
		mutex:                  &sync.Mutex{},
		layerStatus:            map[string]progressLine{},
		reportFn:               reportFn,
		layersChangedFn:        layersChangedFn,
		progressTimeThreshhold: defaultProgressTimeThreshhold,
		stableThreshhold:       defaultStableThreshhold,
	}
	return writer
}

type imageProgressWriter struct {
	mutex                  *sync.Mutex
	internalWriter         io.Writer
	layerStatus            map[string]progressLine
	lastLayerCount         int
	stableLines            int
	stableThreshhold       int
	progressTimeThreshhold time.Duration
	lastReport             report
	lastReportTime         time.Time
	reportFn               func(report)
	layersChangedFn        func(report, report) bool
}

func (w *imageProgressWriter) ReadFrom(reader io.Reader) (int64, error) {
	decoder := json.NewDecoder(reader)
	return 0, w.readProgress(decoder)
}

func (w *imageProgressWriter) Write(data []byte) (int, error) {
	w.mutex.Lock()
	defer w.mutex.Unlock()
	if w.internalWriter == nil {
		var pipeIn *io.PipeReader
		pipeIn, w.internalWriter = io.Pipe()
		decoder := json.NewDecoder(pipeIn)
		go func() {
			err := w.readProgress(decoder)
			if err != nil {
				pipeIn.CloseWithError(err)
			}
		}()
	}
	return w.internalWriter.Write(data)
}

func (w *imageProgressWriter) readProgress(decoder *json.Decoder) error {
	for {
		line := &progressLine{}
		err := decoder.Decode(line)
		if err == io.EOF {
			break
		}
		if err != nil {
			return err
		}
		err = w.processLine(line)
		if err != nil {
			return err
		}
	}
	return nil
}

func (w *imageProgressWriter) processLine(line *progressLine) error {

	if err := getError(line); err != nil {
		return err
	}

	// determine if it's a line we want to process
	if !islayerStatus(line) {
		return nil
	}

	w.layerStatus[line.ID] = *line

	// if the number of layers has not stabilized yet, return and wait for more
	// progress
	if !w.isStableLayerCount() {
		return nil
	}

	r := createReport(w.layerStatus)

	// check if the count of layers in each state has changed
	if w.layersChangedFn(w.lastReport, r) {
		w.lastReport = r
		w.lastReportTime = time.Now()
		w.reportFn(r)
		return nil
	}
	// If layer counts haven't changed, but enough time has passed (30 sec by default),
	// at least report on download/push progress
	if time.Since(w.lastReportTime) > w.progressTimeThreshhold {
		w.lastReport = r
		w.lastReportTime = time.Now()
		w.reportFn(r)
	}
	return nil
}

func (w *imageProgressWriter) isStableLayerCount() bool {
	// If the number of layers has changed since last status, we're not stable
	if w.lastLayerCount != len(w.layerStatus) {
		w.lastLayerCount = len(w.layerStatus)
		w.stableLines = 0
		return false
	}
	// Only proceed after we've received status for the same number
	// of layers at least stableThreshhold times. If not, they're still increasing
	w.stableLines++
	if w.stableLines < w.stableThreshhold {
		// We're not stable enough yet
		return false
	}

	return true
}

var layerIDRegexp = regexp.MustCompile("^[a-f,0-9]*$")

func islayerStatus(line *progressLine) bool {
	// ignore status lines with no layer id
	if len(line.ID) == 0 {
		return false
	}
	// ignore layer ids that are not hex string
	if !layerIDRegexp.MatchString(line.ID) {
		return false
	}
	// ignore retrying status
	if strings.HasPrefix(line.Status, "Retrying") {
		return false
	}
	return true
}

func getError(line *progressLine) error {
	if len(line.Error) > 0 {
		return errors.New(line.Error)
	}
	return nil
}

func createReport(dockerProgress map[string]progressLine) report {
	r := report{}
	for _, line := range dockerProgress {
		layerStatus := layerStatusFromDockerString(line.Status)
		detail, exists := r[layerStatus]
		if !exists {
			detail = &layerDetail{}
			r[layerStatus] = detail
		}
		detail.Count++
		if line.Detail != nil {
			detail.Current += line.Detail.Current
			detail.Total += line.Detail.Total
		}
	}
	return r
}
<|endoftext|>


<commit_before>// Copyright (c) 2012-2014 Jeremy Latt
// Copyright (c) 2014-2015 Edmund Huber
// Copyright (c) 2016-2017 Daniel Oaks <daniel@danieloaks.net>
// released under the MIT license

package irc

import (
	"github.com/goshuirc/irc-go/ircmsg"
	"github.com/oragono/oragono/irc/modes"
)

// Command represents a command accepted from a client.
type Command struct {
	handler         func(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool
	oper            bool
	usablePreReg    bool
	leaveClientIdle bool // if true, leaves the client active time alone
	allowedInBatch  bool // allowed in client-to-server batches
	minParams       int
	capabs          []string
}

// Run runs this command with the given client/message.
func (cmd *Command) Run(server *Server, client *Client, session *Session, msg ircmsg.IrcMessage) (exiting bool) {
	rb := NewResponseBuffer(session)
	rb.Label = GetLabel(msg)

	exiting = func() bool {
		defer rb.Send(true)

		if !client.registered && !cmd.usablePreReg {
			rb.Add(nil, server.name, ERR_NOTREGISTERED, "*", client.t("You need to register before you can use that command"))
			return false
		}
		if cmd.oper && !client.HasMode(modes.Operator) {
			rb.Add(nil, server.name, ERR_NOPRIVILEGES, client.Nick(), client.t("Permission Denied - You're not an IRC operator"))
			return false
		}
		if len(cmd.capabs) > 0 && !client.HasRoleCapabs(cmd.capabs...) {
			rb.Add(nil, server.name, ERR_NOPRIVILEGES, client.Nick(), client.t("Permission Denied"))
			return false
		}
		if len(msg.Params) < cmd.minParams {
			rb.Add(nil, server.name, ERR_NEEDMOREPARAMS, client.Nick(), msg.Command, rb.target.t("Not enough parameters"))
			return false
		}
		if session.batch.label != "" && !cmd.allowedInBatch {
			rb.Add(nil, server.name, "FAIL", "BATCH", "MULTILINE_INVALID", client.t("Command not allowed during a multiline batch"))
			session.batch = MultilineBatch{}
			return false
		}

		return cmd.handler(server, client, msg, rb)
	}()

	// most servers do this only for PING/PONG, but we'll do it for any command:
	if client.registered {
		// touch even if `exiting`, so we record the time of a QUIT accurately
		session.idletimer.Touch()
	}

	if exiting {
		return
	}

	// after each command, see if we can send registration to the client
	if !client.registered {
		exiting = server.tryRegister(client, session)
	}

	if client.registered && !cmd.leaveClientIdle {
		client.Active(session)
	}

	return exiting
}

// Commands holds all commands executable by a client connected to us.
var Commands map[string]Command

func init() {
	Commands = map[string]Command{
		"AMBIANCE": {
			handler:   sceneHandler,
			minParams: 2,
		},
		"AUTHENTICATE": {
			handler:      authenticateHandler,
			usablePreReg: true,
			minParams:    1,
		},
		"AWAY": {
			handler:   awayHandler,
			minParams: 0,
		},
		"BATCH": {
			handler:        batchHandler,
			minParams:      1,
			allowedInBatch: true,
		},
		"BRB": {
			handler:   brbHandler,
			minParams: 0,
		},
		"CAP": {
			handler:      capHandler,
			usablePreReg: true,
			minParams:    1,
		},
		"CHATHISTORY": {
			handler:   chathistoryHandler,
			minParams: 4,
		},
		"DEBUG": {
			handler:   debugHandler,
			minParams: 1,
			oper:      true,
		},
		"DEOPER": {
			handler:   deoperHandler,
			minParams: 0,
			oper:      true,
		},
		"DLINE": {
			handler:   dlineHandler,
			minParams: 1,
			oper:      true,
		},
		"HELP": {
			handler:   helpHandler,
			minParams: 0,
		},
		"HELPOP": {
			handler:   helpHandler,
			minParams: 0,
		},
		"HISTORY": {
			handler:   historyHandler,
			minParams: 1,
		},
		"INFO": {
			handler: infoHandler,
		},
		"INVITE": {
			handler:   inviteHandler,
			minParams: 2,
		},
		"ISON": {
			handler:         isonHandler,
			minParams:       1,
			leaveClientIdle: true,
		},
		"JOIN": {
			handler:   joinHandler,
			minParams: 1,
		},
		"KICK": {
			handler:   kickHandler,
			minParams: 2,
		},
		"KILL": {
			handler:   killHandler,
			minParams: 1,
			oper:      true,
			capabs:    []string{"oper:local_kill"}, //TODO(dan): when we have S2S, this will be checked in the command handler itself
		},
		"KLINE": {
			handler:   klineHandler,
			minParams: 1,
			oper:      true,
		},
		"LANGUAGE": {
			handler:      languageHandler,
			usablePreReg: true,
			minParams:    1,
		},
		"LIST": {
			handler:   listHandler,
			minParams: 0,
		},
		"LUSERS": {
			handler:   lusersHandler,
			minParams: 0,
		},
		"MODE": {
			handler:   modeHandler,
			minParams: 1,
		},
		"MONITOR": {
			handler:   monitorHandler,
			minParams: 1,
		},
		"MOTD": {
			handler:   motdHandler,
			minParams: 0,
		},
		"NAMES": {
			handler:   namesHandler,
			minParams: 0,
		},
		"NICK": {
			handler:      nickHandler,
			usablePreReg: true,
			minParams:    1,
		},
		"NOTICE": {
			handler:        messageHandler,
			minParams:      2,
			allowedInBatch: true,
		},
		"NPC": {
			handler:   npcHandler,
			minParams: 3,
		},
		"NPCA": {
			handler:   npcaHandler,
			minParams: 3,
		},
		"OPER": {
			handler:   operHandler,
			minParams: 1,
		},
		"PART": {
			handler:   partHandler,
			minParams: 1,
		},
		"PASS": {
			handler:      passHandler,
			usablePreReg: true,
			minParams:    1,
		},
		"PING": {
			handler:         pingHandler,
			usablePreReg:    true,
			minParams:       1,
			leaveClientIdle: true,
		},
		"PONG": {
			handler:         pongHandler,
			usablePreReg:    true,
			minParams:       1,
			leaveClientIdle: true,
		},
		"PRIVMSG": {
			handler:        messageHandler,
			minParams:      2,
			allowedInBatch: true,
		},
		"RENAME": {
			handler:   renameHandler,
			minParams: 2,
		},
		"RESUME": {
			handler:      resumeHandler,
			usablePreReg: true,
			minParams:    1,
		},
		"SAJOIN": {
			handler:   sajoinHandler,
			minParams: 1,
			capabs:    []string{"sajoin"},
		},
		"SANICK": {
			handler:   sanickHandler,
			minParams: 2,
			oper:      true,
		},
		"SAMODE": {
			handler:   modeHandler,
			minParams: 1,
			capabs:    []string{"samode"},
		},
		"SCENE": {
			handler:   sceneHandler,
			minParams: 2,
		},
		"SETNAME": {
			handler:   setnameHandler,
			minParams: 1,
		},
		"TAGMSG": {
			handler:   messageHandler,
			minParams: 1,
		},
		"QUIT": {
			handler:      quitHandler,
			usablePreReg: true,
			minParams:    0,
		},
		"REHASH": {
			handler:   rehashHandler,
			minParams: 0,
			oper:      true,
			capabs:    []string{"oper:rehash"},
		},
		"TIME": {
			handler:   timeHandler,
			minParams: 0,
		},
		"TOPIC": {
			handler:   topicHandler,
			minParams: 1,
		},
		"UNDLINE": {
			handler:   unDLineHandler,
			minParams: 1,
			oper:      true,
		},
		"UNKLINE": {
			handler:   unKLineHandler,
			minParams: 1,
			oper:      true,
		},
		"USER": {
			handler:      userHandler,
			usablePreReg: true,
			minParams:    4,
		},
		"USERHOST": {
			handler:   userhostHandler,
			minParams: 1,
		},
		"VERSION": {
			handler:   versionHandler,
			minParams: 0,
		},
		"WEBIRC": {
			handler:      webircHandler,
			usablePreReg: true,
			minParams:    4,
		},
		"WHO": {
			handler:         whoHandler,
			minParams:       1,
			leaveClientIdle: true,
		},
		"WHOIS": {
			handler:   whoisHandler,
			minParams: 1,
		},
		"WHOWAS": {
			handler:   whowasHandler,
			minParams: 1,
		},
		"ZNC": {
			handler:   zncHandler,
			minParams: 1,
		},
	}

	initializeServices()
}
<commit_msg>fix #814<commit_after>// Copyright (c) 2012-2014 Jeremy Latt
// Copyright (c) 2014-2015 Edmund Huber
// Copyright (c) 2016-2017 Daniel Oaks <daniel@danieloaks.net>
// released under the MIT license

package irc

import (
	"github.com/goshuirc/irc-go/ircmsg"
	"github.com/oragono/oragono/irc/modes"
)

// Command represents a command accepted from a client.
type Command struct {
	handler         func(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool
	oper            bool
	usablePreReg    bool
	leaveClientIdle bool // if true, leaves the client active time alone
	allowedInBatch  bool // allowed in client-to-server batches
	minParams       int
	capabs          []string
}

// Run runs this command with the given client/message.
func (cmd *Command) Run(server *Server, client *Client, session *Session, msg ircmsg.IrcMessage) (exiting bool) {
	rb := NewResponseBuffer(session)
	rb.Label = GetLabel(msg)

	exiting = func() bool {
		defer rb.Send(true)

		if !client.registered && !cmd.usablePreReg {
			rb.Add(nil, server.name, ERR_NOTREGISTERED, "*", client.t("You need to register before you can use that command"))
			return false
		}
		if cmd.oper && !client.HasMode(modes.Operator) {
			rb.Add(nil, server.name, ERR_NOPRIVILEGES, client.Nick(), client.t("Permission Denied - You're not an IRC operator"))
			return false
		}
		if len(cmd.capabs) > 0 && !client.HasRoleCapabs(cmd.capabs...) {
			rb.Add(nil, server.name, ERR_NOPRIVILEGES, client.Nick(), client.t("Permission Denied"))
			return false
		}
		if len(msg.Params) < cmd.minParams {
			rb.Add(nil, server.name, ERR_NEEDMOREPARAMS, client.Nick(), msg.Command, rb.target.t("Not enough parameters"))
			return false
		}
		if session.batch.label != "" && !cmd.allowedInBatch {
			rb.Add(nil, server.name, "FAIL", "BATCH", "MULTILINE_INVALID", client.t("Command not allowed during a multiline batch"))
			session.batch = MultilineBatch{}
			return false
		}

		return cmd.handler(server, client, msg, rb)
	}()

	// after each command, see if we can send registration to the client
	if !exiting && !client.registered {
		exiting = server.tryRegister(client, session)
	}

	// most servers do this only for PING/PONG, but we'll do it for any command:
	if client.registered {
		// touch even if `exiting`, so we record the time of a QUIT accurately
		session.idletimer.Touch()
	}

	if !exiting && client.registered && !cmd.leaveClientIdle {
		client.Active(session)
	}

	return exiting
}

// Commands holds all commands executable by a client connected to us.
var Commands map[string]Command

func init() {
	Commands = map[string]Command{
		"AMBIANCE": {
			handler:   sceneHandler,
			minParams: 2,
		},
		"AUTHENTICATE": {
			handler:      authenticateHandler,
			usablePreReg: true,
			minParams:    1,
		},
		"AWAY": {
			handler:   awayHandler,
			minParams: 0,
		},
		"BATCH": {
			handler:        batchHandler,
			minParams:      1,
			allowedInBatch: true,
		},
		"BRB": {
			handler:   brbHandler,
			minParams: 0,
		},
		"CAP": {
			handler:      capHandler,
			usablePreReg: true,
			minParams:    1,
		},
		"CHATHISTORY": {
			handler:   chathistoryHandler,
			minParams: 4,
		},
		"DEBUG": {
			handler:   debugHandler,
			minParams: 1,
			oper:      true,
		},
		"DEOPER": {
			handler:   deoperHandler,
			minParams: 0,
			oper:      true,
		},
		"DLINE": {
			handler:   dlineHandler,
			minParams: 1,
			oper:      true,
		},
		"HELP": {
			handler:   helpHandler,
			minParams: 0,
		},
		"HELPOP": {
			handler:   helpHandler,
			minParams: 0,
		},
		"HISTORY": {
			handler:   historyHandler,
			minParams: 1,
		},
		"INFO": {
			handler: infoHandler,
		},
		"INVITE": {
			handler:   inviteHandler,
			minParams: 2,
		},
		"ISON": {
			handler:         isonHandler,
			minParams:       1,
			leaveClientIdle: true,
		},
		"JOIN": {
			handler:   joinHandler,
			minParams: 1,
		},
		"KICK": {
			handler:   kickHandler,
			minParams: 2,
		},
		"KILL": {
			handler:   killHandler,
			minParams: 1,
			oper:      true,
			capabs:    []string{"oper:local_kill"}, //TODO(dan): when we have S2S, this will be checked in the command handler itself
		},
		"KLINE": {
			handler:   klineHandler,
			minParams: 1,
			oper:      true,
		},
		"LANGUAGE": {
			handler:      languageHandler,
			usablePreReg: true,
			minParams:    1,
		},
		"LIST": {
			handler:   listHandler,
			minParams: 0,
		},
		"LUSERS": {
			handler:   lusersHandler,
			minParams: 0,
		},
		"MODE": {
			handler:   modeHandler,
			minParams: 1,
		},
		"MONITOR": {
			handler:   monitorHandler,
			minParams: 1,
		},
		"MOTD": {
			handler:   motdHandler,
			minParams: 0,
		},
		"NAMES": {
			handler:   namesHandler,
			minParams: 0,
		},
		"NICK": {
			handler:      nickHandler,
			usablePreReg: true,
			minParams:    1,
		},
		"NOTICE": {
			handler:        messageHandler,
			minParams:      2,
			allowedInBatch: true,
		},
		"NPC": {
			handler:   npcHandler,
			minParams: 3,
		},
		"NPCA": {
			handler:   npcaHandler,
			minParams: 3,
		},
		"OPER": {
			handler:   operHandler,
			minParams: 1,
		},
		"PART": {
			handler:   partHandler,
			minParams: 1,
		},
		"PASS": {
			handler:      passHandler,
			usablePreReg: true,
			minParams:    1,
		},
		"PING": {
			handler:         pingHandler,
			usablePreReg:    true,
			minParams:       1,
			leaveClientIdle: true,
		},
		"PONG": {
			handler:         pongHandler,
			usablePreReg:    true,
			minParams:       1,
			leaveClientIdle: true,
		},
		"PRIVMSG": {
			handler:        messageHandler,
			minParams:      2,
			allowedInBatch: true,
		},
		"RENAME": {
			handler:   renameHandler,
			minParams: 2,
		},
		"RESUME": {
			handler:      resumeHandler,
			usablePreReg: true,
			minParams:    1,
		},
		"SAJOIN": {
			handler:   sajoinHandler,
			minParams: 1,
			capabs:    []string{"sajoin"},
		},
		"SANICK": {
			handler:   sanickHandler,
			minParams: 2,
			oper:      true,
		},
		"SAMODE": {
			handler:   modeHandler,
			minParams: 1,
			capabs:    []string{"samode"},
		},
		"SCENE": {
			handler:   sceneHandler,
			minParams: 2,
		},
		"SETNAME": {
			handler:   setnameHandler,
			minParams: 1,
		},
		"TAGMSG": {
			handler:   messageHandler,
			minParams: 1,
		},
		"QUIT": {
			handler:      quitHandler,
			usablePreReg: true,
			minParams:    0,
		},
		"REHASH": {
			handler:   rehashHandler,
			minParams: 0,
			oper:      true,
			capabs:    []string{"oper:rehash"},
		},
		"TIME": {
			handler:   timeHandler,
			minParams: 0,
		},
		"TOPIC": {
			handler:   topicHandler,
			minParams: 1,
		},
		"UNDLINE": {
			handler:   unDLineHandler,
			minParams: 1,
			oper:      true,
		},
		"UNKLINE": {
			handler:   unKLineHandler,
			minParams: 1,
			oper:      true,
		},
		"USER": {
			handler:      userHandler,
			usablePreReg: true,
			minParams:    4,
		},
		"USERHOST": {
			handler:   userhostHandler,
			minParams: 1,
		},
		"VERSION": {
			handler:   versionHandler,
			minParams: 0,
		},
		"WEBIRC": {
			handler:      webircHandler,
			usablePreReg: true,
			minParams:    4,
		},
		"WHO": {
			handler:         whoHandler,
			minParams:       1,
			leaveClientIdle: true,
		},
		"WHOIS": {
			handler:   whoisHandler,
			minParams: 1,
		},
		"WHOWAS": {
			handler:   whowasHandler,
			minParams: 1,
		},
		"ZNC": {
			handler:   zncHandler,
			minParams: 1,
		},
	}

	initializeServices()
}
<|endoftext|>


<commit_before>package irc

import (
	"strings"
	"fmt"
	"time"
	"strconv"
)

func (irc *IRCConnection) AddCallback(eventcode string, callback func(*IRCEvent)) {
	eventcode = strings.ToUpper(eventcode)
	if event, ok := irc.events[eventcode]; ok {
		// TODO: Grow this dynamically
		event = event[0 : len(event)+1]
		event[len(event)-1] = callback
	} else {
		event = make([]func(*IRCEvent), 1, 20)
		event[0] = callback
		irc.events[eventcode] = event
	}
}

func (irc *IRCConnection) RunCallbacks(event *IRCEvent) {
	if event.Code == "PRIVMSG" && event.Message[0] == '\x01' {
		event.Code = "CTCP" //Unknown CTCP
		if i := strings.LastIndex(event.Message, "\x01"); i > -1 {
			event.Message = event.Message[1:i]
		}
		if event.Message == "VERSION" {
			event.Code = "CTCP_VERSION"
		} else if event.Message == "TIME" {
			event.Code = "CTCP_TIME"
		} else if event.Message[0:4] == "PING" {
			event.Code = "CTCP_PING"
		} else if event.Message == "USERINFO" {
			event.Code = "CTCP_USERINFO"
		} else if event.Message == "CLIENTINFO" {
			event.Code = "CTCP_CLIENTINFO"
		}
	}
	if callbacks, ok := irc.events[event.Code]; ok {
		for _, callback := range callbacks {
			go callback(event)
		}
	} else {
		fmt.Printf("No callback for: %#v\n", event)
	}
}

func (irc *IRCConnection) setupCallbacks() {
	irc.events = make(map[string][]func(*IRCEvent))

	//Handle ping events
	irc.AddCallback("PING", func(e *IRCEvent) { irc.SendRaw("PONG :" + e.Message) })

	//Version handler
	irc.AddCallback("CTCP_VERSION", func(e *IRCEvent) {
		irc.SendRaw(fmt.Sprintf("NOTICE %s :\x01VERSION %s\x01", e.Nick, VERSION))
	})

	irc.AddCallback("CTCP_USERINFO", func(e *IRCEvent) {
		irc.SendRaw(fmt.Sprintf("NOTICE %s :\x01USERINFO %s\x01", e.Nick, irc.user))
	})

	irc.AddCallback("CTCP_CLIENTINFO", func(e *IRCEvent) {
		irc.SendRaw(fmt.Sprintf("NOTICE %s :\x01CLIENTINFO PING VERSION TIME USERINFO CLIENTINFO\x01", e.Nick))
	})

	irc.AddCallback("CTCP_TIME", func(e *IRCEvent) {
		irc.SendRaw(fmt.Sprintf("NOTICE %s :\x01TIME %s\x01", e.Nick, time.LocalTime().Asctime()))
	})

	irc.AddCallback("CTCP_PING", func(e *IRCEvent) { irc.SendRaw(fmt.Sprintf("NOTICE %s :\x01%s\x01", e.Nick, e.Message)) })

	irc.AddCallback("437", func(e *IRCEvent) {
		irc.nick = irc.nick + "_"
		irc.SendRaw(fmt.Sprintf("NICK %s", irc.nick))
	})

	irc.AddCallback("PONG", func(e *IRCEvent) {
		ns, _ := strconv.Atoi64(e.Message)
		fmt.Printf("Lag: %fs\n", float((time.Nanoseconds()-ns))/1000/1000/1000)
	})
}
<commit_msg>Compile fix for new go version of time<commit_after>package irc

import (
	"strings"
	"fmt"
	"time"
	"strconv"
)

func (irc *IRCConnection) AddCallback(eventcode string, callback func(*IRCEvent)) {
	eventcode = strings.ToUpper(eventcode)
	if event, ok := irc.events[eventcode]; ok {
		// TODO: Grow this dynamically
		event = event[0 : len(event)+1]
		event[len(event)-1] = callback
	} else {
		event = make([]func(*IRCEvent), 1, 20)
		event[0] = callback
		irc.events[eventcode] = event
	}
}

func (irc *IRCConnection) RunCallbacks(event *IRCEvent) {
	if event.Code == "PRIVMSG" && event.Message[0] == '\x01' {
		event.Code = "CTCP" //Unknown CTCP
		if i := strings.LastIndex(event.Message, "\x01"); i > -1 {
			event.Message = event.Message[1:i]
		}
		if event.Message == "VERSION" {
			event.Code = "CTCP_VERSION"
		} else if event.Message == "TIME" {
			event.Code = "CTCP_TIME"
		} else if event.Message[0:4] == "PING" {
			event.Code = "CTCP_PING"
		} else if event.Message == "USERINFO" {
			event.Code = "CTCP_USERINFO"
		} else if event.Message == "CLIENTINFO" {
			event.Code = "CTCP_CLIENTINFO"
		}
	}
	if callbacks, ok := irc.events[event.Code]; ok {
		for _, callback := range callbacks {
			go callback(event)
		}
	} else {
		fmt.Printf("No callback for: %#v\n", event)
	}
}

func (irc *IRCConnection) setupCallbacks() {
	irc.events = make(map[string][]func(*IRCEvent))

	//Handle ping events
	irc.AddCallback("PING", func(e *IRCEvent) { irc.SendRaw("PONG :" + e.Message) })

	//Version handler
	irc.AddCallback("CTCP_VERSION", func(e *IRCEvent) {
		irc.SendRaw(fmt.Sprintf("NOTICE %s :\x01VERSION %s\x01", e.Nick, VERSION))
	})

	irc.AddCallback("CTCP_USERINFO", func(e *IRCEvent) {
		irc.SendRaw(fmt.Sprintf("NOTICE %s :\x01USERINFO %s\x01", e.Nick, irc.user))
	})

	irc.AddCallback("CTCP_CLIENTINFO", func(e *IRCEvent) {
		irc.SendRaw(fmt.Sprintf("NOTICE %s :\x01CLIENTINFO PING VERSION TIME USERINFO CLIENTINFO\x01", e.Nick))
	})

	irc.AddCallback("CTCP_TIME", func(e *IRCEvent) {
		irc.SendRaw(fmt.Sprintf("NOTICE %s :\x01TIME %s\x01", e.Nick, time.LocalTime().Format("ANSIC")))
	})

	irc.AddCallback("CTCP_PING", func(e *IRCEvent) { irc.SendRaw(fmt.Sprintf("NOTICE %s :\x01%s\x01", e.Nick, e.Message)) })

	irc.AddCallback("437", func(e *IRCEvent) {
		irc.nick = irc.nick + "_"
		irc.SendRaw(fmt.Sprintf("NICK %s", irc.nick))
	})

	irc.AddCallback("PONG", func(e *IRCEvent) {
		ns, _ := strconv.Atoi64(e.Message)
		fmt.Printf("Lag: %fs\n", float((time.Nanoseconds()-ns))/1000/1000/1000)
	})
}
<|endoftext|>


<commit_before>package geoip2

import (
	"fmt"
	"io/fs"
	"log"
	"net"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"github.com/abh/geodns/countries"
	"github.com/abh/geodns/targeting/geo"
	geoip2 "github.com/oschwald/geoip2-golang"
)

type geoType uint8

const (
	countryDB = iota
	cityDB
	asnDB
)

var dbFiles map[geoType][]string

// GeoIP2 contains the geoip implementation of the GeoDNS geo
// targeting interface
type GeoIP2 struct {
	dir string

	country geodb
	city    geodb
	asn     geodb

	mu sync.RWMutex
}

type geodb struct {
	db           *geoip2.Reader // Database reader
	fp           string         // FilePath
	lastModified int64          // Epoch time
	// l            sync.Mutex     // Individual lock for separate DB access and reload -- Future?
}

func init() {
	dbFiles = map[geoType][]string{
		countryDB: {"GeoIP2-Country.mmdb", "GeoLite2-Country.mmdb"},
		asnDB:     {"GeoIP2-ASN.mmdb", "GeoLite2-ASN.mmdb"},
		cityDB:    {"GeoIP2-City.mmdb", "GeoLite2-City.mmdb"},
	}
}

// FindDB returns a guess at a directory path for GeoIP data files
func FindDB() string {
	dirs := []string{
		"/usr/share/GeoIP/",       // Linux default
		"/usr/share/local/GeoIP/", // source install?
		"/usr/local/share/GeoIP/", // FreeBSD
		"/opt/local/share/GeoIP/", // MacPorts
	}
	for _, dir := range dirs {
		if _, err := os.Stat(dir); err != nil {
			if os.IsExist(err) {
				log.Println(err)
			}
			continue
		}
		return dir
	}
	return ""
}

func (g *GeoIP2) open(t geoType, db string) (*geoip2.Reader, error) {
	fileName := filepath.Join(g.dir, db)
	var fi fs.FileInfo

	if len(db) == 0 {
		found := false
		for _, f := range dbFiles[t] {
			var err error
			fileName = filepath.Join(g.dir, f)
			if fi, err = os.Stat(fileName); err == nil {
				found = true
				break
			}
		}
		if !found {
			return nil, fmt.Errorf("could not find '%s' in '%s'", dbFiles[t], g.dir)
		}
	}

	n, err := geoip2.Open(fileName)
	if err != nil {
		return nil, err
	}
	g.mu.Lock()
	defer g.mu.Unlock()

	switch t {
	case countryDB:
		g.country.db = n
		g.country.lastModified = fi.ModTime().UTC().Unix()
		g.country.fp = fileName
	case cityDB:
		g.city.db = n
		g.city.lastModified = fi.ModTime().UTC().Unix()
		g.city.fp = fileName
	case asnDB:
		g.asn.db = n
		g.asn.lastModified = fi.ModTime().UTC().Unix()
		g.asn.fp = fileName
	}
	return n, nil
}

func (g *GeoIP2) get(t geoType, db string) (*geoip2.Reader, error) {
	g.mu.RLock()

	var r *geoip2.Reader

	switch t {
	case countryDB:
		r = g.country.db
	case cityDB:
		r = g.city.db
	case asnDB:
		r = g.asn.db
	}

	// unlock so the g.open() call below won't lock
	g.mu.RUnlock()

	if r != nil {
		return r, nil
	}

	return g.open(t, db)
}

func (g *GeoIP2) watchFiles() {
	// Not worried about goroutines leaking because only one geoip2.New call is made in geodns.go (outside of testing)
	ticker := time.NewTicker(1 * time.Minute)
	go func() {
		for {
			select {
			case <-ticker.C:
				g.checkForUpdate()
			default:
				time.Sleep(12 * time.Second) // Sleep to avoid constant looping
			}
		}
	}()
}

func (g *GeoIP2) checkForUpdate() {
	// Iterate through each file, check modtime. If new, reload file
	d := []*geodb{&g.country, &g.city, &g.asn} // Slice of pointers is kinda gross, but want to directly reference struct values (per const type)
	for _, v := range d {
		fi, err := os.Stat(v.fp)
		if err != nil {
			log.Printf("unable to stat DB file at %s :: %v", v.fp, err)
			continue
		}
		if fi.ModTime().UTC().Unix() > v.lastModified {
			g.mu.Lock()
			e := v.db.Close()
			if e != nil {
				g.mu.Unlock()
				log.Printf("unable to close DB file %s : %v", v.fp, e)
				continue
			}
			n, e := geoip2.Open(v.fp)
			if e != nil {
				g.mu.Unlock()
				log.Printf("unable to reopen DB file %s : %v", v.fp, e)
				continue
			}
			v.db = n
			g.mu.Unlock()
		}
	}
}

// New returns a new GeoIP2 provider
func New(dir string) (*GeoIP2, error) {
	g := &GeoIP2{
		dir: dir,
	}
	_, err := g.open(countryDB, "")
	if err != nil {
		return nil, err
	}

	go g.watchFiles() // Launch goroutine to monitor

	return g, nil
}

// HasASN returns if we can do ASN lookups
func (g *GeoIP2) HasASN() (bool, error) {
	r, err := g.get(asnDB, "")
	if r != nil && err == nil {
		return true, nil
	}
	return false, err
}

// GetASN returns the ASN for the IP (as a "as123" string) and the netmask
func (g *GeoIP2) GetASN(ip net.IP) (string, int, error) {
	r, err := g.get(asnDB, "")
	log.Printf("GetASN for %s, got DB? %s", ip, err)
	if err != nil {
		return "", 0, err
	}

	c, err := r.ASN(ip)
	if err != nil {
		return "", 0, fmt.Errorf("lookup ASN for '%s': %s", ip.String(), err)
	}
	asn := c.AutonomousSystemNumber
	netmask := 24
	if ip.To4() != nil {
		netmask = 48
	}
	return fmt.Sprintf("as%d", asn), netmask, nil
}

// HasCountry checks if the GeoIP country database is available
func (g *GeoIP2) HasCountry() (bool, error) {
	r, err := g.get(countryDB, "")
	if r != nil && err == nil {
		return true, nil
	}
	return false, err
}

// GetCountry returns the country, continent and netmask for the given IP
func (g *GeoIP2) GetCountry(ip net.IP) (country, continent string, netmask int) {
	r, err := g.get(countryDB, "")
	c, err := r.Country(ip)
	if err != nil {
		log.Printf("Could not lookup country for '%s': %s", ip.String(), err)
		return "", "", 0
	}

	country = c.Country.IsoCode

	if len(country) > 0 {
		country = strings.ToLower(country)
		continent = countries.CountryContinent[country]
	}

	return country, continent, 0
}

// HasLocation returns if the city database is available to
// return lat/lon information for an IP
func (g *GeoIP2) HasLocation() (bool, error) {
	r, err := g.get(cityDB, "")
	if r != nil && err == nil {
		return true, nil
	}
	return false, err
}

// GetLocation returns a geo.Location object for the given IP
func (g *GeoIP2) GetLocation(ip net.IP) (l *geo.Location, err error) {
	c, err := g.city.db.City(ip)
	if err != nil {
		log.Printf("Could not lookup CountryRegion for '%s': %s", ip.String(), err)
		return
	}

	l = &geo.Location{
		Latitude:  float64(c.Location.Latitude),
		Longitude: float64(c.Location.Longitude),
		Country:   strings.ToLower(c.Country.IsoCode),
	}

	if len(c.Subdivisions) > 0 {
		l.Region = strings.ToLower(c.Subdivisions[0].IsoCode)
	}
	if len(l.Country) > 0 {
		l.Continent = countries.CountryContinent[l.Country]
		if len(l.Region) > 0 {
			l.Region = l.Country + "-" + l.Region
			l.RegionGroup = countries.CountryRegionGroup(l.Country, l.Region)
		}
	}

	return
}
<commit_msg>geoip2: add read lock to GetLocation<commit_after>package geoip2

import (
	"fmt"
	"io/fs"
	"log"
	"net"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"github.com/abh/geodns/countries"
	"github.com/abh/geodns/targeting/geo"
	geoip2 "github.com/oschwald/geoip2-golang"
)

type geoType uint8

const (
	countryDB = iota
	cityDB
	asnDB
)

var dbFiles map[geoType][]string

// GeoIP2 contains the geoip implementation of the GeoDNS geo
// targeting interface
type GeoIP2 struct {
	dir string

	country geodb
	city    geodb
	asn     geodb

	mu sync.RWMutex
}

type geodb struct {
	db           *geoip2.Reader // Database reader
	fp           string         // FilePath
	lastModified int64          // Epoch time
	// l            sync.Mutex     // Individual lock for separate DB access and reload -- Future?
}

func init() {
	dbFiles = map[geoType][]string{
		countryDB: {"GeoIP2-Country.mmdb", "GeoLite2-Country.mmdb"},
		asnDB:     {"GeoIP2-ASN.mmdb", "GeoLite2-ASN.mmdb"},
		cityDB:    {"GeoIP2-City.mmdb", "GeoLite2-City.mmdb"},
	}
}

// FindDB returns a guess at a directory path for GeoIP data files
func FindDB() string {
	dirs := []string{
		"/usr/share/GeoIP/",       // Linux default
		"/usr/share/local/GeoIP/", // source install?
		"/usr/local/share/GeoIP/", // FreeBSD
		"/opt/local/share/GeoIP/", // MacPorts
	}
	for _, dir := range dirs {
		if _, err := os.Stat(dir); err != nil {
			if os.IsExist(err) {
				log.Println(err)
			}
			continue
		}
		return dir
	}
	return ""
}

func (g *GeoIP2) open(t geoType, db string) (*geoip2.Reader, error) {
	fileName := filepath.Join(g.dir, db)
	var fi fs.FileInfo

	if len(db) == 0 {
		found := false
		for _, f := range dbFiles[t] {
			var err error
			fileName = filepath.Join(g.dir, f)
			if fi, err = os.Stat(fileName); err == nil {
				found = true
				break
			}
		}
		if !found {
			return nil, fmt.Errorf("could not find '%s' in '%s'", dbFiles[t], g.dir)
		}
	}

	n, err := geoip2.Open(fileName)
	if err != nil {
		return nil, err
	}
	g.mu.Lock()
	defer g.mu.Unlock()

	switch t {
	case countryDB:
		g.country.db = n
		g.country.lastModified = fi.ModTime().UTC().Unix()
		g.country.fp = fileName
	case cityDB:
		g.city.db = n
		g.city.lastModified = fi.ModTime().UTC().Unix()
		g.city.fp = fileName
	case asnDB:
		g.asn.db = n
		g.asn.lastModified = fi.ModTime().UTC().Unix()
		g.asn.fp = fileName
	}
	return n, nil
}

func (g *GeoIP2) get(t geoType, db string) (*geoip2.Reader, error) {
	g.mu.RLock()

	var r *geoip2.Reader

	switch t {
	case countryDB:
		r = g.country.db
	case cityDB:
		r = g.city.db
	case asnDB:
		r = g.asn.db
	}

	// unlock so the g.open() call below won't lock
	g.mu.RUnlock()

	if r != nil {
		return r, nil
	}

	return g.open(t, db)
}

func (g *GeoIP2) watchFiles() {
	// Not worried about goroutines leaking because only one geoip2.New call is made in geodns.go (outside of testing)
	ticker := time.NewTicker(1 * time.Minute)
	go func() {
		for {
			select {
			case <-ticker.C:
				g.checkForUpdate()
			default:
				time.Sleep(12 * time.Second) // Sleep to avoid constant looping
			}
		}
	}()
}

func (g *GeoIP2) checkForUpdate() {
	// Iterate through each file, check modtime. If new, reload file
	d := []*geodb{&g.country, &g.city, &g.asn} // Slice of pointers is kinda gross, but want to directly reference struct values (per const type)
	for _, v := range d {
		fi, err := os.Stat(v.fp)
		if err != nil {
			log.Printf("unable to stat DB file at %s :: %v", v.fp, err)
			continue
		}
		if fi.ModTime().UTC().Unix() > v.lastModified {
			g.mu.Lock()
			e := v.db.Close()
			if e != nil {
				g.mu.Unlock()
				log.Printf("unable to close DB file %s : %v", v.fp, e)
				continue
			}
			n, e := geoip2.Open(v.fp)
			if e != nil {
				g.mu.Unlock()
				log.Printf("unable to reopen DB file %s : %v", v.fp, e)
				continue
			}
			v.db = n
			g.mu.Unlock()
		}
	}
}

// New returns a new GeoIP2 provider
func New(dir string) (*GeoIP2, error) {
	g := &GeoIP2{
		dir: dir,
	}
	_, err := g.open(countryDB, "")
	if err != nil {
		return nil, err
	}

	go g.watchFiles() // Launch goroutine to monitor

	return g, nil
}

// HasASN returns if we can do ASN lookups
func (g *GeoIP2) HasASN() (bool, error) {
	r, err := g.get(asnDB, "")
	if r != nil && err == nil {
		return true, nil
	}
	return false, err
}

// GetASN returns the ASN for the IP (as a "as123" string) and the netmask
func (g *GeoIP2) GetASN(ip net.IP) (string, int, error) {
	r, err := g.get(asnDB, "")
	log.Printf("GetASN for %s, got DB? %s", ip, err)
	if err != nil {
		return "", 0, err
	}

	c, err := r.ASN(ip)
	if err != nil {
		return "", 0, fmt.Errorf("lookup ASN for '%s': %s", ip.String(), err)
	}
	asn := c.AutonomousSystemNumber
	netmask := 24
	if ip.To4() != nil {
		netmask = 48
	}
	return fmt.Sprintf("as%d", asn), netmask, nil
}

// HasCountry checks if the GeoIP country database is available
func (g *GeoIP2) HasCountry() (bool, error) {
	r, err := g.get(countryDB, "")
	if r != nil && err == nil {
		return true, nil
	}
	return false, err
}

// GetCountry returns the country, continent and netmask for the given IP
func (g *GeoIP2) GetCountry(ip net.IP) (country, continent string, netmask int) {
	r, err := g.get(countryDB, "")
	c, err := r.Country(ip)
	if err != nil {
		log.Printf("Could not lookup country for '%s': %s", ip.String(), err)
		return "", "", 0
	}

	country = c.Country.IsoCode

	if len(country) > 0 {
		country = strings.ToLower(country)
		continent = countries.CountryContinent[country]
	}

	return country, continent, 0
}

// HasLocation returns if the city database is available to
// return lat/lon information for an IP
func (g *GeoIP2) HasLocation() (bool, error) {
	r, err := g.get(cityDB, "")
	if r != nil && err == nil {
		return true, nil
	}
	return false, err
}

// GetLocation returns a geo.Location object for the given IP
func (g *GeoIP2) GetLocation(ip net.IP) (l *geo.Location, err error) {
	// Need a read-lock because return value of City is a pointer, not copy of the struct/object
	g.mu.RLock()
	defer g.mu.RUnlock()

	c, err := g.city.db.City(ip)
	if err != nil {
		log.Printf("Could not lookup CountryRegion for '%s': %s", ip.String(), err)
		return
	}

	l = &geo.Location{
		Latitude:  float64(c.Location.Latitude),
		Longitude: float64(c.Location.Longitude),
		Country:   strings.ToLower(c.Country.IsoCode),
	}

	if len(c.Subdivisions) > 0 {
		l.Region = strings.ToLower(c.Subdivisions[0].IsoCode)
	}
	if len(l.Country) > 0 {
		l.Continent = countries.CountryContinent[l.Country]
		if len(l.Region) > 0 {
			l.Region = l.Country + "-" + l.Region
			l.RegionGroup = countries.CountryRegionGroup(l.Country, l.Region)
		}
	}

	return
}
<|endoftext|>


<commit_before>package stripe

import "encoding/json"

// IssuingCardShippingStatus is the list of possible values for the shipping status
// on an issuing card.
type IssuingCardShippingStatus string

// List of values that IssuingCardShippingStatus can take.
const (
	IssuingCardShippingTypeDelivered IssuingCardShippingStatus = "delivered"
	IssuingCardShippingTypeFailure   IssuingCardShippingStatus = "failure"
	IssuingCardShippingTypePending   IssuingCardShippingStatus = "pending"
	IssuingCardShippingTypeReturned  IssuingCardShippingStatus = "returned"
	IssuingCardShippingTypeShipped   IssuingCardShippingStatus = "shipped"
)

// IssuingCardShippingType is the list of possible values for the shipping type
// on an issuing card.
type IssuingCardShippingType string

// List of values that IssuingCardShippingType can take.
const (
	IssuingCardShippingTypeBulk       IssuingCardShippingType = "bulk"
	IssuingCardShippingTypeIndividual IssuingCardShippingType = "individual"
)

// IssuingCardStatus is the list of possible values for status on an issuing card.
type IssuingCardStatus string

// List of values that IssuingCardStatus can take.
const (
	IssuingCardStatusActive   IssuingCardStatus = "active"
	IssuingCardStatusCanceled IssuingCardStatus = "canceled"
	IssuingCardStatusInactive IssuingCardStatus = "inactive"
	IssuingCardStatusPending  IssuingCardStatus = "pending"
)

// IssuingCardType is the type of an issuing card.
type IssuingCardType string

// List of values that IssuingCardType can take.
const (
	IssuingCardTypePhysical IssuingCardType = "physical"
	IssuingCardTypeVirtual  IssuingCardType = "virtual"
)

// AuthorizationControlsParams is the set of parameters that can be used for the shipping parameter.
type AuthorizationControlsParams struct {
	AllowedCategories []*string `form:"allowed_categories"`
	BlockedCategories []*string `form:"blocked_categories"`
	MaxAmount         *int64    `form:"max_amount"`
	MaxApprovals      *int64    `form:"max_approvals"`
}

// IssuingCardShippingParams is the set of parameters that can be used for the shipping parameter.
type IssuingCardShippingParams struct {
	Address *AddressParams `form:"address"`
	Name    string         `form:"name"`
	Type    *string        `form:"type"`
}

// IssuingCardParams is the set of parameters that can be used when creating or updating an issuing card.
type IssuingCardParams struct {
	Params                `form:"*"`
	AuthorizationControls *AuthorizationControlsParams `form:"authorization_controls"`
	Billing               *IssuingBillingParams        `form:"billing"`
	Cardholder            *string                      `form:"cardholder"`
	Currency              *string                      `form:"currency"`
	Name                  *string                      `form:"name"`
	Status                *string                      `form:"status"`
	Shipping              *IssuingCardShippingParams   `form:"shipping"`
	Type                  *string                      `form:"type"`
}

// IssuingCardListParams is the set of parameters that can be used when listing issuing cards.
type IssuingCardListParams struct {
	ListParams   `form:"*"`
	Cardholder   *string           `form:"cardholder"`
	Created      *int64            `form:"created"`
	CreatedRange *RangeQueryParams `form:"created"`
	ExpMonth     *int64            `form:"exp_month"`
	ExpYear      *int64            `form:"exp_year"`
	Last4        *string           `form:"last4"`
	Source       *string           `form:"source"`
	Status       *string           `form:"status"`
	Type         *string           `form:"type"`
}

// IssuingCardDetails is the resource representing issuing card details.
type IssuingCardDetails struct {
	Card     *IssuingCard `json:"card"`
	CVC      string       `json:"cvc"`
	ExpMonth *string      `form:"exp_month"`
	ExpYear  *string      `form:"exp_year"`
	Number   string       `json:"type"`
	Object   string       `json:"object"`
}

// IssuingCardAuthorizationControls is the resource representing authorization controls on an issuing card.
type IssuingCardAuthorizationControls struct {
	AllowedCategories []string `json:"allowed_categories"`
	BlockedCategories []string `json:"blocked_categories"`
	Currency          Currency `json:"currency"`
	MaxAmount         int64    `json:"max_amount"`
	MaxApprovals      int64    `json:"max_approvals"`
}

// IssuingCardShipping is the resource representing shipping on an issuing card.
type IssuingCardShipping struct {
	Address        *Address                  `json:"address"`
	Carrier        string                    `json:"carrier"`
	Eta            int64                     `json:"eta"`
	Name           string                    `json:"name"`
	Phone          string                    `json:"phone"`
	Status         IssuingCardShippingStatus `json:"status"`
	TrackingNumber string                    `json:"tracking_number"`
	Type           IssuingCardShippingType   `json:"type"`
}

// IssuingCard is the resource representing a Stripe issuing card.
type IssuingCard struct {
	AuthorizationControls *IssuingCardAuthorizationControls `json:"authorization_controls"`
	Billing               *IssuingBilling                   `json:"billing"`
	Brand                 string                            `json:"brand"`
	Cardholder            *IssuingCardholder                `json:"cardholder"`
	Created               int64                             `json:"created"`
	ExpMonth              int64                             `json:"exp_month"`
	ExpYear               int64                             `json:"exp_year"`
	Last4                 string                            `json:"last4"`
	ID                    string                            `json:"id"`
	Livemode              bool                              `json:"livemode"`
	Metadata              map[string]string                 `json:"metadata"`
	Name                  string                            `json:"name"`
	Object                string                            `json:"object"`
	Shipping              *IssuingCardShipping              `json:"shipping"`
	Status                IssuingCardStatus                 `json:"status"`
	Type                  IssuingCardType                   `json:"type"`
}

// IssuingCardList is a list of issuing cards as retrieved from a list endpoint.
type IssuingCardList struct {
	ListMeta
	Data []*IssuingCard `json:"data"`
}

// UnmarshalJSON handles deserialization of an IssuingCard.
// This custom unmarshaling is needed because the resulting
// property may be an id or the full struct if it was expanded.
func (i *IssuingCard) UnmarshalJSON(data []byte) error {
	if id, ok := ParseID(data); ok {
		i.ID = id
		return nil
	}

	type issuingCard IssuingCard
	var v issuingCard
	if err := json.Unmarshal(data, &v); err != nil {
		return err
	}

	*i = IssuingCard(v)
	return nil
}
<commit_msg>Rename `IssuingCardShipping`'s `Eta` to `ETA` to fit conventions<commit_after>package stripe

import "encoding/json"

// IssuingCardShippingStatus is the list of possible values for the shipping status
// on an issuing card.
type IssuingCardShippingStatus string

// List of values that IssuingCardShippingStatus can take.
const (
	IssuingCardShippingTypeDelivered IssuingCardShippingStatus = "delivered"
	IssuingCardShippingTypeFailure   IssuingCardShippingStatus = "failure"
	IssuingCardShippingTypePending   IssuingCardShippingStatus = "pending"
	IssuingCardShippingTypeReturned  IssuingCardShippingStatus = "returned"
	IssuingCardShippingTypeShipped   IssuingCardShippingStatus = "shipped"
)

// IssuingCardShippingType is the list of possible values for the shipping type
// on an issuing card.
type IssuingCardShippingType string

// List of values that IssuingCardShippingType can take.
const (
	IssuingCardShippingTypeBulk       IssuingCardShippingType = "bulk"
	IssuingCardShippingTypeIndividual IssuingCardShippingType = "individual"
)

// IssuingCardStatus is the list of possible values for status on an issuing card.
type IssuingCardStatus string

// List of values that IssuingCardStatus can take.
const (
	IssuingCardStatusActive   IssuingCardStatus = "active"
	IssuingCardStatusCanceled IssuingCardStatus = "canceled"
	IssuingCardStatusInactive IssuingCardStatus = "inactive"
	IssuingCardStatusPending  IssuingCardStatus = "pending"
)

// IssuingCardType is the type of an issuing card.
type IssuingCardType string

// List of values that IssuingCardType can take.
const (
	IssuingCardTypePhysical IssuingCardType = "physical"
	IssuingCardTypeVirtual  IssuingCardType = "virtual"
)

// AuthorizationControlsParams is the set of parameters that can be used for the shipping parameter.
type AuthorizationControlsParams struct {
	AllowedCategories []*string `form:"allowed_categories"`
	BlockedCategories []*string `form:"blocked_categories"`
	MaxAmount         *int64    `form:"max_amount"`
	MaxApprovals      *int64    `form:"max_approvals"`
}

// IssuingCardShippingParams is the set of parameters that can be used for the shipping parameter.
type IssuingCardShippingParams struct {
	Address *AddressParams `form:"address"`
	Name    string         `form:"name"`
	Type    *string        `form:"type"`
}

// IssuingCardParams is the set of parameters that can be used when creating or updating an issuing card.
type IssuingCardParams struct {
	Params                `form:"*"`
	AuthorizationControls *AuthorizationControlsParams `form:"authorization_controls"`
	Billing               *IssuingBillingParams        `form:"billing"`
	Cardholder            *string                      `form:"cardholder"`
	Currency              *string                      `form:"currency"`
	Name                  *string                      `form:"name"`
	Status                *string                      `form:"status"`
	Shipping              *IssuingCardShippingParams   `form:"shipping"`
	Type                  *string                      `form:"type"`
}

// IssuingCardListParams is the set of parameters that can be used when listing issuing cards.
type IssuingCardListParams struct {
	ListParams   `form:"*"`
	Cardholder   *string           `form:"cardholder"`
	Created      *int64            `form:"created"`
	CreatedRange *RangeQueryParams `form:"created"`
	ExpMonth     *int64            `form:"exp_month"`
	ExpYear      *int64            `form:"exp_year"`
	Last4        *string           `form:"last4"`
	Source       *string           `form:"source"`
	Status       *string           `form:"status"`
	Type         *string           `form:"type"`
}

// IssuingCardDetails is the resource representing issuing card details.
type IssuingCardDetails struct {
	Card     *IssuingCard `json:"card"`
	CVC      string       `json:"cvc"`
	ExpMonth *string      `form:"exp_month"`
	ExpYear  *string      `form:"exp_year"`
	Number   string       `json:"type"`
	Object   string       `json:"object"`
}

// IssuingCardAuthorizationControls is the resource representing authorization controls on an issuing card.
type IssuingCardAuthorizationControls struct {
	AllowedCategories []string `json:"allowed_categories"`
	BlockedCategories []string `json:"blocked_categories"`
	Currency          Currency `json:"currency"`
	MaxAmount         int64    `json:"max_amount"`
	MaxApprovals      int64    `json:"max_approvals"`
}

// IssuingCardShipping is the resource representing shipping on an issuing card.
type IssuingCardShipping struct {
	Address        *Address                  `json:"address"`
	Carrier        string                    `json:"carrier"`
	ETA            int64                     `json:"eta"`
	Name           string                    `json:"name"`
	Phone          string                    `json:"phone"`
	Status         IssuingCardShippingStatus `json:"status"`
	TrackingNumber string                    `json:"tracking_number"`
	Type           IssuingCardShippingType   `json:"type"`
}

// IssuingCard is the resource representing a Stripe issuing card.
type IssuingCard struct {
	AuthorizationControls *IssuingCardAuthorizationControls `json:"authorization_controls"`
	Billing               *IssuingBilling                   `json:"billing"`
	Brand                 string                            `json:"brand"`
	Cardholder            *IssuingCardholder                `json:"cardholder"`
	Created               int64                             `json:"created"`
	ExpMonth              int64                             `json:"exp_month"`
	ExpYear               int64                             `json:"exp_year"`
	Last4                 string                            `json:"last4"`
	ID                    string                            `json:"id"`
	Livemode              bool                              `json:"livemode"`
	Metadata              map[string]string                 `json:"metadata"`
	Name                  string                            `json:"name"`
	Object                string                            `json:"object"`
	Shipping              *IssuingCardShipping              `json:"shipping"`
	Status                IssuingCardStatus                 `json:"status"`
	Type                  IssuingCardType                   `json:"type"`
}

// IssuingCardList is a list of issuing cards as retrieved from a list endpoint.
type IssuingCardList struct {
	ListMeta
	Data []*IssuingCard `json:"data"`
}

// UnmarshalJSON handles deserialization of an IssuingCard.
// This custom unmarshaling is needed because the resulting
// property may be an id or the full struct if it was expanded.
func (i *IssuingCard) UnmarshalJSON(data []byte) error {
	if id, ok := ParseID(data); ok {
		i.ID = id
		return nil
	}

	type issuingCard IssuingCard
	var v issuingCard
	if err := json.Unmarshal(data, &v); err != nil {
		return err
	}

	*i = IssuingCard(v)
	return nil
}
<|endoftext|>