prompt_hub.go 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. package main
  2. import (
  3. "fmt"
  4. "io"
  5. "os"
  6. "time"
  7. "github.com/howeyc/gopass"
  8. )
  9. type Prompt struct {
  10. resp chan PromptResponse
  11. cancel chan struct{}
  12. ID string `json:"id"`
  13. Kind string `json:"kind"`
  14. Deadline time.Time `json:"deadline"`
  15. Message string `json:"message"`
  16. }
  17. type PromptResponse struct {
  18. ID string `json:"id"`
  19. Value string `json:"value"`
  20. Err error `json:"error"`
  21. }
  22. type PromptHub struct {
  23. c chan *Prompt
  24. rc chan PromptResponse
  25. omitTerminal bool
  26. }
  27. func NewPromptHub() *PromptHub { p := new(PromptHub); go p.loop(); return p }
  28. func (p *PromptHub) OmitTerminal(t bool) { p.omitTerminal = t }
  29. func (p *PromptHub) loop() {
  30. p.c = make(chan *Prompt, 0)
  31. p.rc = make(chan PromptResponse, 0)
  32. for prompt := range p.c {
  33. timeout := time.After(prompt.Deadline.Sub(time.Now()))
  34. select {
  35. case <-timeout:
  36. prompt.resp <- PromptResponse{ID: prompt.ID, Err: fmt.Errorf("Deadline reached")}
  37. close(prompt.cancel)
  38. case resp := <-p.rc:
  39. if resp.ID != prompt.ID {
  40. continue
  41. }
  42. select {
  43. case prompt.resp <- resp:
  44. default:
  45. }
  46. close(prompt.cancel)
  47. }
  48. }
  49. }
  50. func (p *PromptHub) Respond(id, value string, err error) {
  51. select {
  52. case p.rc <- PromptResponse{ID: id, Value: value, Err: err}:
  53. default:
  54. }
  55. }
  56. func (p *PromptHub) Prompt(kind, message string) <-chan PromptResponse {
  57. prompt := &Prompt{
  58. resp: make(chan PromptResponse),
  59. cancel: make(chan struct{}), // Closed on cancel (e.g. prompt response received)
  60. ID: fmt.Sprint(time.Now().UnixNano()),
  61. Kind: kind,
  62. Message: message,
  63. Deadline: time.Now().Add(time.Minute),
  64. }
  65. p.c <- prompt
  66. websocketHub.Prompt(*prompt)
  67. if !p.omitTerminal {
  68. go p.promptTerminal(*prompt)
  69. }
  70. return prompt.resp
  71. }
  72. type ReadAborter struct {
  73. *os.File
  74. abort chan struct{}
  75. }
  76. func (r ReadAborter) Read(p []byte) (int, error) {
  77. tick := time.Tick(100 * time.Millisecond)
  78. for {
  79. select {
  80. case <-r.abort:
  81. return 0, io.EOF
  82. case <-tick:
  83. stat, err := r.Stat()
  84. if err != nil {
  85. panic(err)
  86. }
  87. if stat.Size() > 0 {
  88. return r.File.Read(p)
  89. }
  90. }
  91. }
  92. }
  93. func (p *PromptHub) promptTerminal(prompt Prompt) {
  94. switch prompt.Kind {
  95. case "password":
  96. q := make(chan struct{}, 1)
  97. go func() {
  98. select {
  99. case <-prompt.cancel:
  100. fmt.Printf(" Prompt Aborted - Press ENTER to continue...")
  101. case <-q:
  102. return
  103. }
  104. }()
  105. passwd, err := gopass.GetPasswdPrompt(prompt.Message+": ", true, os.Stdin, os.Stdout)
  106. q <- struct{}{}
  107. p.Respond(prompt.ID, string(passwd), err)
  108. default:
  109. panic(prompt.Kind + " prompt not implemented")
  110. }
  111. }