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  }