responseemitter.go 3.07 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
var _ ResponseEmitter = &responseEmitter{}

Jan Winkelmann's avatar
Jan Winkelmann committed
16 17 18 19
type ErrSet struct {
	error
}

Jan Winkelmann's avatar
Jan Winkelmann committed
20 21
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)
22
	encType := cmds.GetEncoding(req)
Jan Winkelmann's avatar
Jan Winkelmann committed
23

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

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

35 36 37 38 39 40 41 42 43
// 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
44
type responseEmitter struct {
45 46
	wLock sync.Mutex
	w     io.WriteCloser
Jan Winkelmann's avatar
Jan Winkelmann committed
47

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

53
	ch chan<- *cmdsutil.Error
Jan Winkelmann's avatar
Jan Winkelmann committed
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 100 101
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
102
func (re *responseEmitter) Emit(v interface{}) error {
103 104 105 106 107 108 109 110 111 112 113 114 115 116
	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
117 118
	if v == nil {
	}
Jan Winkelmann's avatar
Jan Winkelmann committed
119

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

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

Jan Winkelmann's avatar
Jan Winkelmann committed
132 133 134
	var err error

	switch t := v.(type) {
135 136 137 138 139
	// 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
140
	case io.Reader:
Jan Winkelmann's avatar
Jan Winkelmann committed
141
		_, err = io.Copy(re.w, t)
142 143 144 145
		if err != nil {
			re.SetError(err, cmdsutil.ErrNormal)
			err = nil
		}
Jan Winkelmann's avatar
Jan Winkelmann committed
146 147 148 149 150 151 152 153 154 155
	default:
		if re.enc != nil {
			err = re.enc.Encode(v)
		} else {
			_, err = fmt.Fprintln(re.w, t)
		}
	}

	return err
}
156 157 158 159 160 161 162 163 164 165 166 167

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)
}