github.com/guilhermebr/docker@v1.4.2-0.20150428121140-67da055cebca/engine/streams.go (about) 1 package engine 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "strings" 8 "sync" 9 "unicode" 10 ) 11 12 type Output struct { 13 sync.Mutex 14 dests []io.Writer 15 tasks sync.WaitGroup 16 used bool 17 } 18 19 // Tail returns the n last lines of a buffer 20 // stripped out of trailing white spaces, if any. 21 // 22 // if n <= 0, returns an empty string 23 func Tail(buffer *bytes.Buffer, n int) string { 24 if n <= 0 { 25 return "" 26 } 27 s := strings.TrimRightFunc(buffer.String(), unicode.IsSpace) 28 i := len(s) - 1 29 for ; i >= 0 && n > 0; i-- { 30 if s[i] == '\n' { 31 n-- 32 if n == 0 { 33 break 34 } 35 } 36 } 37 // when i == -1, return the whole string which is s[0:] 38 return s[i+1:] 39 } 40 41 // NewOutput returns a new Output object with no destinations attached. 42 // Writing to an empty Output will cause the written data to be discarded. 43 func NewOutput() *Output { 44 return &Output{} 45 } 46 47 // Return true if something was written on this output 48 func (o *Output) Used() bool { 49 o.Lock() 50 defer o.Unlock() 51 return o.used 52 } 53 54 // Add attaches a new destination to the Output. Any data subsequently written 55 // to the output will be written to the new destination in addition to all the others. 56 // This method is thread-safe. 57 func (o *Output) Add(dst io.Writer) { 58 o.Lock() 59 defer o.Unlock() 60 o.dests = append(o.dests, dst) 61 } 62 63 // Set closes and remove existing destination and then attaches a new destination to 64 // the Output. Any data subsequently written to the output will be written to the new 65 // destination in addition to all the others. This method is thread-safe. 66 func (o *Output) Set(dst io.Writer) { 67 o.Close() 68 o.Lock() 69 defer o.Unlock() 70 o.dests = []io.Writer{dst} 71 } 72 73 // AddPipe creates an in-memory pipe with io.Pipe(), adds its writing end as a destination, 74 // and returns its reading end for consumption by the caller. 75 // This is a rough equivalent similar to Cmd.StdoutPipe() in the standard os/exec package. 76 // This method is thread-safe. 77 func (o *Output) AddPipe() (io.Reader, error) { 78 r, w := io.Pipe() 79 o.Add(w) 80 return r, nil 81 } 82 83 // Write writes the same data to all registered destinations. 84 // This method is thread-safe. 85 func (o *Output) Write(p []byte) (n int, err error) { 86 o.Lock() 87 defer o.Unlock() 88 o.used = true 89 var firstErr error 90 for _, dst := range o.dests { 91 _, err := dst.Write(p) 92 if err != nil && firstErr == nil { 93 firstErr = err 94 } 95 } 96 return len(p), firstErr 97 } 98 99 // Close unregisters all destinations and waits for all background 100 // AddTail and AddString tasks to complete. 101 // The Close method of each destination is called if it exists. 102 func (o *Output) Close() error { 103 o.Lock() 104 defer o.Unlock() 105 var firstErr error 106 for _, dst := range o.dests { 107 if closer, ok := dst.(io.Closer); ok { 108 err := closer.Close() 109 if err != nil && firstErr == nil { 110 firstErr = err 111 } 112 } 113 } 114 o.tasks.Wait() 115 o.dests = nil 116 return firstErr 117 } 118 119 type Input struct { 120 src io.Reader 121 sync.Mutex 122 } 123 124 // NewInput returns a new Input object with no source attached. 125 // Reading to an empty Input will return io.EOF. 126 func NewInput() *Input { 127 return &Input{} 128 } 129 130 // Read reads from the input in a thread-safe way. 131 func (i *Input) Read(p []byte) (n int, err error) { 132 i.Mutex.Lock() 133 defer i.Mutex.Unlock() 134 if i.src == nil { 135 return 0, io.EOF 136 } 137 return i.src.Read(p) 138 } 139 140 // Closes the src 141 // Not thread safe on purpose 142 func (i *Input) Close() error { 143 if i.src != nil { 144 if closer, ok := i.src.(io.Closer); ok { 145 return closer.Close() 146 } 147 } 148 return nil 149 } 150 151 // Add attaches a new source to the input. 152 // Add can only be called once per input. Subsequent calls will 153 // return an error. 154 func (i *Input) Add(src io.Reader) error { 155 i.Mutex.Lock() 156 defer i.Mutex.Unlock() 157 if i.src != nil { 158 return fmt.Errorf("Maximum number of sources reached: 1") 159 } 160 i.src = src 161 return nil 162 } 163 164 // AddEnv starts a new goroutine which will decode all subsequent data 165 // as a stream of json-encoded objects, and point `dst` to the last 166 // decoded object. 167 // The result `env` can be queried using the type-neutral Env interface. 168 // It is not safe to query `env` until the Output is closed. 169 func (o *Output) AddEnv() (dst *Env, err error) { 170 src, err := o.AddPipe() 171 if err != nil { 172 return nil, err 173 } 174 dst = &Env{} 175 o.tasks.Add(1) 176 go func() { 177 defer o.tasks.Done() 178 decoder := NewDecoder(src) 179 for { 180 env, err := decoder.Decode() 181 if err != nil { 182 return 183 } 184 *dst = *env 185 } 186 }() 187 return dst, nil 188 }