forked from la5nta/pat
-
Notifications
You must be signed in to change notification settings - Fork 0
/
prompt_hub.go
127 lines (111 loc) · 2.53 KB
/
prompt_hub.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
package main
import (
"fmt"
"io"
"os"
"time"
"github.com/howeyc/gopass"
)
type Prompt struct {
resp chan PromptResponse
cancel chan struct{}
ID string `json:"id"`
Kind string `json:"kind"`
Deadline time.Time `json:"deadline"`
Message string `json:"message"`
}
type PromptResponse struct {
ID string `json:"id"`
Value string `json:"value"`
Err error `json:"error"`
}
type PromptHub struct {
c chan *Prompt
rc chan PromptResponse
omitTerminal bool
}
func NewPromptHub() *PromptHub { p := new(PromptHub); go p.loop(); return p }
func (p *PromptHub) OmitTerminal(t bool) { p.omitTerminal = t }
func (p *PromptHub) loop() {
p.c = make(chan *Prompt, 0)
p.rc = make(chan PromptResponse, 0)
for prompt := range p.c {
timeout := time.After(prompt.Deadline.Sub(time.Now()))
select {
case <-timeout:
prompt.resp <- PromptResponse{ID: prompt.ID, Err: fmt.Errorf("Deadline reached")}
close(prompt.cancel)
case resp := <-p.rc:
if resp.ID != prompt.ID {
continue
}
select {
case prompt.resp <- resp:
default:
}
close(prompt.cancel)
}
}
}
func (p *PromptHub) Respond(id, value string, err error) {
select {
case p.rc <- PromptResponse{ID: id, Value: value, Err: err}:
default:
}
}
func (p *PromptHub) Prompt(kind, message string) <-chan PromptResponse {
prompt := &Prompt{
resp: make(chan PromptResponse),
cancel: make(chan struct{}), // Closed on cancel (e.g. prompt response received)
ID: fmt.Sprint(time.Now().UnixNano()),
Kind: kind,
Message: message,
Deadline: time.Now().Add(time.Minute),
}
p.c <- prompt
websocketHub.Prompt(*prompt)
if !p.omitTerminal {
go p.promptTerminal(*prompt)
}
return prompt.resp
}
type ReadAborter struct {
*os.File
abort chan struct{}
}
func (r ReadAborter) Read(p []byte) (int, error) {
tick := time.Tick(100 * time.Millisecond)
for {
select {
case <-r.abort:
return 0, io.EOF
case <-tick:
stat, err := r.Stat()
if err != nil {
panic(err)
}
if stat.Size() > 0 {
return r.File.Read(p)
}
}
}
}
func (p *PromptHub) promptTerminal(prompt Prompt) {
switch prompt.Kind {
case "password":
q := make(chan struct{}, 1)
go func() {
select {
case <-prompt.cancel:
fmt.Printf(" Prompt Aborted - Press ENTER to continue...")
case <-q:
return
}
}()
passwd, err := gopass.GetPasswdPrompt(prompt.Message+": ", true, os.Stdin, os.Stdout)
q <- struct{}{}
p.Respond(prompt.ID, string(passwd), err)
default:
panic(prompt.Kind + " prompt not implemented")
}
}