responseemitter.go 3.03 KB
Newer Older
Jan Winkelmann's avatar
Jan Winkelmann committed
1 2 3 4 5
package cli

import (
	"fmt"
	"io"
Jan Winkelmann's avatar
Jan Winkelmann committed
6
	"os"
Jan Winkelmann's avatar
Jan Winkelmann committed
7
	"runtime/debug"
8
	"sync"
Jan Winkelmann's avatar
Jan Winkelmann committed
9 10

	"github.com/ipfs/go-ipfs-cmds"
11
	"gx/ipfs/QmWdiBLZ22juGtuNceNbvvHV11zKzCaoQFMP76x2w1XDFZ/go-ipfs-cmdkit"
Jan Winkelmann's avatar
Jan Winkelmann committed
12 13 14 15 16 17
)

type ErrSet struct {
	error
}

Jan Winkelmann's avatar
Jan Winkelmann committed
18 19
func NewResponseEmitter(w io.WriteCloser, enc func(cmds.Request) func(io.Writer) cmds.Encoder, req cmds.Request) (cmds.ResponseEmitter, <-chan *cmdsutil.Error) {
	ch := make(chan *cmdsutil.Error)
20
	encType := cmds.GetEncoding(req)
Jan Winkelmann's avatar
Jan Winkelmann committed
21

Jan Winkelmann's avatar
Jan Winkelmann committed
22
	if enc == nil {
Jan Winkelmann's avatar
Jan Winkelmann committed
23
		enc = func(cmds.Request) func(io.Writer) cmds.Encoder {
Jan Winkelmann's avatar
Jan Winkelmann committed
24 25 26 27
			return func(io.Writer) cmds.Encoder {
				return nil
			}
		}
Jan Winkelmann's avatar
Jan Winkelmann committed
28 29
	}

30
	return &responseEmitter{w: w, encType: encType, enc: enc(req)(w), ch: ch}, ch
Jan Winkelmann's avatar
Jan Winkelmann committed
31 32
}

33 34 35 36 37 38 39 40 41
// ResponseEmitter extends cmds.ResponseEmitter to give better control over the command line
type ResponseEmitter interface {
	cmds.ResponseEmitter

	Stdout() *os.File
	Stderr() *os.File
	Exit(int)
}

Jan Winkelmann's avatar
Jan Winkelmann committed
42
type responseEmitter struct {
43 44
	wLock sync.Mutex
	w     io.WriteCloser
Jan Winkelmann's avatar
Jan Winkelmann committed
45

46 47 48 49
	length  uint64
	err     *cmdsutil.Error
	enc     cmds.Encoder
	encType cmds.EncodingType
Jan Winkelmann's avatar
Jan Winkelmann committed
50

51
	ch chan<- *cmdsutil.Error
Jan Winkelmann's avatar
Jan Winkelmann committed
52 53
}

54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
func (re *responseEmitter) SetLength(l uint64) {
	re.length = l
}

func (re *responseEmitter) SetEncoder(enc func(io.Writer) cmds.Encoder) {
	re.enc = enc(re.w)
}

func (re *responseEmitter) SetError(v interface{}, errType cmdsutil.ErrorType) {
	err := re.Emit(&cmdsutil.Error{Message: fmt.Sprint(v), Code: errType})
	if err != nil {
		panic(err)
	}
}

func (re *responseEmitter) Close() error {
	re.wLock.Lock()
	defer re.wLock.Unlock()

	if re.w == nil {
		log.Warning("more than one call to RespEm.Close!")
		return nil
	}

	close(re.ch)
	if f, ok := re.w.(*os.File); ok {
		err := f.Sync()
		if err != nil {
			return err
		}
	}
	re.w = nil

	return nil
}

// Head returns the current head.
// TODO: maybe it makes sense to make these pointers to shared memory?
//   might not be so clever though...concurrency and stuff
func (re *responseEmitter) Head() cmds.Head {
	return cmds.Head{
		Len: re.length,
		Err: re.err,
	}
}

Jan Winkelmann's avatar
Jan Winkelmann committed
100
func (re *responseEmitter) Emit(v interface{}) error {
101 102 103 104 105 106 107 108 109 110 111 112 113 114
	if ch, ok := v.(chan interface{}); ok {
		v = (<-chan interface{})(ch)
	}

	if ch, isChan := v.(<-chan interface{}); isChan {
		for v = range ch {
			err := re.Emit(v)
			if err != nil {
				return err
			}
		}
		return nil
	}

Jan Winkelmann's avatar
Jan Winkelmann committed
115 116
	if v == nil {
	}
Jan Winkelmann's avatar
Jan Winkelmann committed
117

118
	re.wLock.Lock()
Jan Winkelmann's avatar
Jan Winkelmann committed
119
	if re.w == nil {
120
		re.wLock.Unlock()
Jan Winkelmann's avatar
Jan Winkelmann committed
121 122
		return io.ErrClosedPipe
	}
123
	re.wLock.Unlock()
Jan Winkelmann's avatar
Jan Winkelmann committed
124 125 126 127 128 129

	if err, ok := v.(cmdsutil.Error); ok {
		log.Warningf("fixerr %s", debug.Stack())
		v = &err
	}

Jan Winkelmann's avatar
Jan Winkelmann committed
130 131 132
	var err error

	switch t := v.(type) {
133 134 135 136 137
	// send errors to the output channel so it will be printed and the program exits
	case *cmdsutil.Error:
		re.ch <- t
		return nil

Jan Winkelmann's avatar
Jan Winkelmann committed
138
	case io.Reader:
Jan Winkelmann's avatar
Jan Winkelmann committed
139
		_, err = io.Copy(re.w, t)
140 141 142 143
		if err != nil {
			re.SetError(err, cmdsutil.ErrNormal)
			err = nil
		}
Jan Winkelmann's avatar
Jan Winkelmann committed
144 145 146 147 148 149 150 151 152 153
	default:
		if re.enc != nil {
			err = re.enc.Encode(v)
		} else {
			_, err = fmt.Fprintln(re.w, t)
		}
	}

	return err
}
154 155 156 157 158 159 160 161 162 163 164 165

func (re *responseEmitter) Stdout() *os.File {
	return os.Stdout
}

func (re *responseEmitter) Stderr() *os.File {
	return os.Stderr
}

func (re *responseEmitter) Exit(code int) {
	os.Exit(code)
}