github.com/mithrandie/csvq@v1.18.1/lib/query/session.go (about) 1 package query 2 3 import ( 4 "bytes" 5 "context" 6 "errors" 7 "io" 8 "os" 9 "sync" 10 "time" 11 12 "github.com/mithrandie/csvq/lib/file" 13 "github.com/mithrandie/csvq/lib/option" 14 "github.com/mithrandie/csvq/lib/parser" 15 16 "golang.org/x/crypto/ssh/terminal" 17 ) 18 19 var ( 20 screenFd = os.Stdin.Fd() 21 stdin io.ReadCloser = os.Stdin 22 stdout io.WriteCloser = os.Stdout 23 stderr io.WriteCloser = os.Stderr 24 ) 25 26 const DefaultScreenWidth = 75 27 28 func isNamedPipe(fp *os.File) bool { 29 fi, err := fp.Stat() 30 if err == nil && (fi.Mode()&os.ModeNamedPipe != 0 || 0 < fi.Size()) { 31 return true 32 } 33 return false 34 } 35 36 type Discard struct { 37 } 38 39 func NewDiscard() *Discard { 40 return &Discard{} 41 } 42 43 func (d Discard) Write(p []byte) (int, error) { 44 return len(p), nil 45 } 46 47 func (d Discard) Close() error { 48 return nil 49 } 50 51 type Input struct { 52 reader io.Reader 53 } 54 55 func NewInput(r io.Reader) *Input { 56 return &Input{reader: r} 57 } 58 59 func (r *Input) Read(p []byte) (int, error) { 60 return r.reader.Read(p) 61 } 62 63 func (r *Input) Close() error { 64 if rc, ok := r.reader.(io.ReadCloser); ok { 65 return rc.Close() 66 } 67 return nil 68 } 69 70 type Output struct { 71 bytes.Buffer 72 } 73 74 func NewOutput() *Output { 75 return &Output{} 76 } 77 78 func (w *Output) Close() error { 79 return nil 80 } 81 82 type StdinLocker struct { 83 mtx *sync.Mutex 84 locked bool 85 rlockCnt int32 86 } 87 88 func NewStdinLocker() *StdinLocker { 89 return &StdinLocker{ 90 mtx: &sync.Mutex{}, 91 locked: false, 92 rlockCnt: 0, 93 } 94 } 95 96 func (cl *StdinLocker) Lock() error { 97 return cl.LockContext(context.Background()) 98 } 99 100 func (cl *StdinLocker) LockContext(ctx context.Context) error { 101 return cl.lockContext(ctx, cl.tryLock) 102 } 103 104 func (cl *StdinLocker) Unlock() (err error) { 105 cl.mtx.Lock() 106 if cl.locked { 107 cl.locked = false 108 } else { 109 err = errors.New("locker is unlocked") 110 } 111 cl.mtx.Unlock() 112 return 113 } 114 115 func (cl *StdinLocker) RLock() error { 116 return cl.RLockContext(context.Background()) 117 } 118 119 func (cl *StdinLocker) RLockContext(ctx context.Context) error { 120 return cl.lockContext(ctx, cl.tryRLock) 121 } 122 123 func (cl *StdinLocker) RUnlock() (err error) { 124 cl.mtx.Lock() 125 if 0 < cl.rlockCnt { 126 cl.rlockCnt-- 127 } else { 128 err = errors.New("locker is unlocked") 129 } 130 cl.mtx.Unlock() 131 return 132 } 133 134 func (cl *StdinLocker) tryLock() bool { 135 cl.mtx.Lock() 136 if cl.locked || 0 < cl.rlockCnt { 137 cl.mtx.Unlock() 138 return false 139 } 140 cl.locked = true 141 cl.mtx.Unlock() 142 return true 143 } 144 145 func (cl *StdinLocker) tryRLock() bool { 146 cl.mtx.Lock() 147 if cl.locked { 148 cl.mtx.Unlock() 149 return false 150 } 151 cl.rlockCnt++ 152 cl.mtx.Unlock() 153 return true 154 } 155 156 func (cl *StdinLocker) lockContext(ctx context.Context, fn func() bool) error { 157 if ctx.Err() != nil { 158 return ConvertContextError(ctx.Err()) 159 } 160 161 for { 162 if fn() { 163 return nil 164 } 165 166 select { 167 case <-ctx.Done(): 168 return NewFileLockTimeoutError(parser.Identifier{Literal: parser.TokenLiteral(parser.STDIN)}) 169 case <-time.After(file.DefaultRetryDelay): 170 // try again 171 } 172 } 173 } 174 175 type Session struct { 176 screenFd uintptr 177 stdin io.ReadCloser 178 stdout io.WriteCloser 179 stderr io.WriteCloser 180 outFile io.Writer 181 terminal VirtualTerminal 182 183 CanReadStdin bool 184 CanOutputToPipe bool 185 186 stdinViewMap ViewMap 187 stdinLocker *StdinLocker 188 189 mtx *sync.Mutex 190 } 191 192 func NewSession() *Session { 193 canReadStdin := isNamedPipe(os.Stdin) 194 canOutputToPipe := isNamedPipe(os.Stdout) 195 196 return &Session{ 197 screenFd: screenFd, 198 stdin: stdin, 199 stdout: stdout, 200 stderr: stderr, 201 outFile: nil, 202 terminal: nil, 203 204 CanReadStdin: canReadStdin, 205 CanOutputToPipe: canOutputToPipe, 206 207 stdinViewMap: NewViewMap(), 208 stdinLocker: NewStdinLocker(), 209 210 mtx: &sync.Mutex{}, 211 } 212 } 213 214 func (sess *Session) ScreenFd() uintptr { 215 return sess.screenFd 216 } 217 218 func (sess *Session) Stdin() io.ReadCloser { 219 return sess.stdin 220 } 221 222 func (sess *Session) Stdout() io.WriteCloser { 223 return sess.stdout 224 } 225 226 func (sess *Session) Stderr() io.WriteCloser { 227 return sess.stderr 228 } 229 230 func (sess *Session) OutFile() io.Writer { 231 return sess.outFile 232 } 233 234 func (sess *Session) Terminal() VirtualTerminal { 235 return sess.terminal 236 } 237 238 func (sess *Session) ScreenWidth() int { 239 width := DefaultScreenWidth 240 if sess.Terminal() != nil { 241 if termw, _, err := sess.Terminal().GetSize(); err == nil { 242 width = termw 243 } 244 } else { 245 if w, _, err := terminal.GetSize(int(sess.ScreenFd())); err == nil { 246 width = w 247 } 248 } 249 return width 250 } 251 252 func (sess *Session) SetStdin(r io.ReadCloser) error { 253 return sess.SetStdinContext(context.Background(), r) 254 } 255 256 func (sess *Session) SetStdinContext(ctx context.Context, r io.ReadCloser) error { 257 if err := sess.stdinLocker.LockContext(ctx); err != nil { 258 return err 259 } 260 261 sess.CanReadStdin = false 262 if r != nil { 263 if fp, ok := r.(*os.File); !ok || (ok && isNamedPipe(fp)) { 264 sess.CanReadStdin = true 265 } 266 } 267 268 sess.stdin = r 269 _ = sess.stdinViewMap.Clean(nil) 270 return sess.stdinLocker.Unlock() 271 } 272 273 func (sess *Session) SetStdout(w io.WriteCloser) { 274 sess.mtx.Lock() 275 sess.stdout = w 276 sess.mtx.Unlock() 277 } 278 279 func (sess *Session) SetStderr(w io.WriteCloser) { 280 sess.mtx.Lock() 281 sess.stderr = w 282 sess.mtx.Unlock() 283 } 284 285 func (sess *Session) SetOutFile(w io.Writer) { 286 sess.mtx.Lock() 287 sess.outFile = w 288 sess.mtx.Unlock() 289 } 290 291 func (sess *Session) SetTerminal(t VirtualTerminal) { 292 sess.mtx.Lock() 293 sess.terminal = t 294 sess.mtx.Unlock() 295 } 296 297 func (sess *Session) GetStdinView(ctx context.Context, flags *option.Flags, fileInfo *FileInfo, expr parser.Stdin) (*View, error) { 298 if !sess.stdinViewMap.Exists(expr.String()) { 299 if !sess.CanReadStdin { 300 return nil, NewStdinEmptyError(expr) 301 } 302 303 view, err := loadViewFromFile(ctx, flags, sess.stdin, fileInfo, flags.ImportOptions, expr) 304 if err != nil { 305 if _, ok := err.(Error); !ok { 306 err = NewDataParsingError(expr, fileInfo.Path, err.Error()) 307 } 308 return nil, err 309 } 310 sess.stdinViewMap.Store(view.FileInfo.IdentifiedPath(), view) 311 } 312 view, err := sess.stdinViewMap.Get(expr.String()) 313 if err != nil { 314 err = NewTableNotLoadedError(parser.Identifier{BaseExpr: expr.BaseExpr, Literal: expr.String()}) 315 } 316 return view, err 317 } 318 319 func (sess *Session) updateStdinView(view *View) { 320 sess.stdinViewMap.Store(view.FileInfo.IdentifiedPath(), view) 321 } 322 323 func (sess *Session) WriteToStdout(s string) (err error) { 324 sess.mtx.Lock() 325 if sess.terminal != nil { 326 err = sess.terminal.Write(s) 327 } else if sess.stdout != nil { 328 _, err = sess.stdout.Write([]byte(s)) 329 } 330 sess.mtx.Unlock() 331 return 332 } 333 334 func (sess *Session) WriteToStdoutWithLineBreak(s string) error { 335 if 0 < len(s) && s[len(s)-1] != '\n' { 336 s = s + "\n" 337 } 338 return sess.WriteToStdout(s) 339 } 340 341 func (sess *Session) WriteToStderr(s string) (err error) { 342 sess.mtx.Lock() 343 if sess.terminal != nil { 344 err = sess.terminal.WriteError(s) 345 } else if sess.stderr != nil { 346 _, err = sess.stderr.Write([]byte(s)) 347 } 348 sess.mtx.Unlock() 349 return 350 } 351 352 func (sess *Session) WriteToStderrWithLineBreak(s string) error { 353 if 0 < len(s) && s[len(s)-1] != '\n' { 354 s = s + "\n" 355 } 356 return sess.WriteToStderr(s) 357 }