api.go 3.41 KB
Newer Older
Philip Kaludercic's avatar
Philip Kaludercic committed
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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
package ablib

import (
	"bytes"
	"encoding/json"
	"io"
	"io/ioutil"
	"net/http"
	"sync"
	"time"
)

// API implements the control-loop for communication with an Adora
// Belle server
type API struct {
	sync.Mutex
	Admin      bool
	Interval   time.Duration
	Base       string
	User, Pass string
	wait       chan error
	cli        *http.Client
	seen       map[string]bool
	used       bool
}

func (api *API) request(method, route string) *http.Request {
	req, err := http.NewRequest(method, api.Base+route, nil)
	if err != nil {
		panic(err)
	}
	req.SetBasicAuth(api.User, api.Pass)
	return req
}

// Run implements the control-loop, that calls the UI interface
//
// The method will panic if called more than once.
// The argument ui may not be nil.
func (api *API) Run(ui UI) error {
	api.Lock()
	if api.used {
		panic("The Run method may only be called once.")
	}
	api.used = true
	api.Unlock()

	if ui == nil {
		panic("UI cannot be nil")
	}

	if api.seen == nil {
		api.seen = make(map[string]bool)
	}
	if api.Interval < time.Millisecond*200 {
		api.Interval = 10 * time.Second
	}
	api.wait = make(chan error)
	api.cli = http.DefaultClient

	// start event listeners
	go api.statusListener(ui)
	go api.clientListener(ui)

	return <-api.wait
}

func (api *API) statusListener(ui UI) {
	var statusReq *http.Request
	if api.Admin {
		statusReq = api.request(http.MethodGet, "admin/requests")
	} else {
		statusReq = api.request(http.MethodGet, "public/requests")
	}

	// start command loop
	for {
		var s status
		api.Lock()
		resp, err := api.cli.Do(statusReq)
		if err != nil {
			api.Unlock()
			ui.HandleError(err)
			goto wait
		}

		err = json.NewDecoder(resp.Body).Decode(s)
		if err != nil {
			api.Unlock()
			ui.HandleError(err)
			goto wait
		}
		if api.Admin {
			err = api.handleAdmin(ui, s)
		} else {
			err = api.handleUser(ui, s)
		}

		// mark connection as reusable
		io.Copy(ioutil.Discard, resp.Body)
		resp.Body.Close()
		api.Unlock()

		if err != nil {
			ui.HandleError(err)
		}

	wait:
		time.Sleep(api.Interval)
	}
}

func (api *API) clientListener(ui UI) {
	var (
		helpReq   = api.request(http.MethodPost, "public/request")
		cancelReq = api.request(http.MethodPost, "public/cancle")
		handleReq = api.request(http.MethodPost, "admin/handle")
		reloadReq = api.request(http.MethodPost, "admin/reload")
	)

	// start command loop
	var buf bytes.Buffer
	for {
		var (
			req  *http.Request
			body interface{}
		)

		cmd, data, arg, err := ui.Listen()
		if err != nil {
			ui.HandleError(err)
			continue
		}

		switch cmd {
		case NoOp:
			continue
		case Close:
			api.wait <- nil
			return
		case Help:
			req = helpReq
			body = data
		case Cancel:
			req = cancelReq
			body = struct {
				Tag     string `json:"tag"`
				Content string `json:"contents"`
			}{
				Tag:     "RequestID",
				Content: data,
			}
		case Handle:
			req = handleReq
			body = [2]interface{}{
				struct {
					Tag     string `json:"tag"`
					Content string `json:"contents"`
				}{
					Tag:     "RequestID",
					Content: data,
				},
				arg,
			}
		case Reload:
			req = reloadReq
		default:
			panic("Unknown command")
		}

		if body != nil {
			buf.Reset()
			err := json.NewEncoder(&buf).Encode(body)
			if err == nil {
				req.Body = ioutil.NopCloser(&buf)
			}
		} else {
			req.Body = nil
		}
		api.Lock()
		resp, err := http.DefaultClient.Do(req)
		io.Copy(ioutil.Discard, resp.Body)
		resp.Body.Close()
		api.Unlock()
		if err != nil {
			ui.HandleError(err)
		}
	}
}