Skip to content

Commit d250fa4

Browse files
authored
Refactor event handling (#72)
Now event handlers can decide which handler will handle the next event. This change simplifies logic to decide who should be handling events by removing a unique, global, point of coordination that was not working well on some situations (e.g. the container menu) and it was quite fragile. It is also easier now to decide what is going to be shown on the screen. A few things still need a good solution: * how to write unit tests for event handlers. * an approach that allows to get rid of global variables.
1 parent aef9478 commit d250fa4

22 files changed

+1200
-1035
lines changed

app/cmenu_events.go

Lines changed: 157 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,177 +1,278 @@
11
package app
22

33
import (
4+
"errors"
45
"fmt"
56
"strings"
67

78
"github.com/moncho/dry/appui"
89
"github.com/moncho/dry/docker"
910
"github.com/moncho/dry/ui"
10-
"github.com/moncho/dry/ui/json"
1111
termbox "github.com/nsf/termbox-go"
1212
)
1313

1414
type cMenuEventHandler struct {
1515
baseEventHandler
1616
}
1717

18-
func (h *cMenuEventHandler) widget() appui.AppWidget {
19-
return h.dry.widgetRegistry.ContainerMenu
20-
}
21-
22-
func (h *cMenuEventHandler) handle(event termbox.Event) {
23-
if h.forwardingEvents {
18+
func (h *cMenuEventHandler) handle(event termbox.Event, f func(eventHandler)) {
19+
if h.forwardingEvents() {
2420
h.eventChan <- event
2521
return
2622
}
27-
handled := false
28-
23+
handled := true
2924
switch event.Key {
3025

3126
case termbox.KeyEsc:
32-
handled = true
3327
h.screen.Cursor.Reset()
34-
h.dry.ShowContainers()
28+
h.dry.SetViewMode(Main)
29+
f(viewsToHandlers[Main])
30+
3531
case termbox.KeyEnter:
36-
handled = true
37-
err := h.widget().OnEvent(func(s string) error {
32+
err := widgets.ContainerMenu.OnEvent(func(s string) error {
3833
//s is a string made of two parts: an Id and a description
3934
//separated by ":"
4035
cd := strings.Split(s, ":")
36+
if len(cd) != 2 {
37+
return errors.New("Invalid command description: " + s)
38+
}
4139
id := cd[0]
4240
command, err := docker.CommandFromDescription(cd[1])
4341
if err != nil {
4442
return err
4543
}
46-
h.handleCommand(id, command)
44+
h.handleCommand(id, command, f)
4745
return nil
4846
})
4947
if err != nil {
5048
h.dry.appmessage(fmt.Sprintf("Could not run command: %s", err.Error()))
5149
}
50+
default:
51+
handled = false
5252
}
5353

5454
if !handled {
55-
h.baseEventHandler.handle(event)
55+
h.baseEventHandler.handle(event, f)
5656
} else {
5757
refreshScreen()
5858
}
5959
}
6060

61-
func (h *cMenuEventHandler) handleCommand(id string, command docker.Command) {
61+
func (h *cMenuEventHandler) handleCommand(id string, command docker.Command, f func(eventHandler)) {
6262

6363
dry := h.dry
6464
screen := h.screen
6565

6666
container := dry.dockerDaemon.ContainerByID(id)
6767
switch command {
6868
case docker.KILL:
69+
prompt := appui.NewPrompt(
70+
fmt.Sprintf("Do you want to kill container %s? (y/N)", id))
71+
widgets.add(prompt)
72+
h.setForwardEvents(true)
73+
6974
go func() {
75+
events := ui.EventSource{
76+
Events: h.eventChan,
77+
EventHandledCallback: func(e termbox.Event) error {
78+
return refreshScreen()
79+
},
80+
}
81+
prompt.OnFocus(events)
82+
conf, cancel := prompt.Text()
83+
h.setForwardEvents(false)
84+
widgets.remove(prompt)
85+
if cancel || (conf != "y" && conf != "Y") {
86+
return
87+
}
88+
7089
dry.actionMessage(id, "Killing")
7190
err := dry.dockerDaemon.Kill(id)
7291
if err == nil {
73-
dry.actionMessage(id, "killed")
92+
widgets.ContainerMenu.ForContainer(id)
93+
refreshScreen()
7494
} else {
7595
dry.errorMessage(id, "killing", err)
7696
}
97+
7798
}()
7899
case docker.RESTART:
100+
101+
prompt := appui.NewPrompt(
102+
fmt.Sprintf("Do you want to restart container %s? (y/N)", id))
103+
widgets.add(prompt)
104+
h.setForwardEvents(true)
105+
79106
go func() {
80-
if err := dry.dockerDaemon.RestartContainer(id); err != nil {
107+
events := ui.EventSource{
108+
Events: h.eventChan,
109+
EventHandledCallback: func(e termbox.Event) error {
110+
return refreshScreen()
111+
},
112+
}
113+
prompt.OnFocus(events)
114+
conf, cancel := prompt.Text()
115+
h.setForwardEvents(false)
116+
widgets.remove(prompt)
117+
if cancel || (conf != "y" && conf != "Y") {
118+
119+
return
120+
}
121+
122+
if err := dry.dockerDaemon.RestartContainer(id); err == nil {
123+
widgets.ContainerMenu.ForContainer(id)
124+
refreshScreen()
125+
} else {
81126
dry.appmessage(
82127
fmt.Sprintf("Error restarting container %s, err: %s", id, err.Error()))
83128
}
129+
84130
}()
131+
85132
case docker.STOP:
133+
134+
prompt := appui.NewPrompt(
135+
fmt.Sprintf("Do you want to stop container %s? (y/N)", id))
136+
widgets.add(prompt)
137+
h.setForwardEvents(true)
138+
86139
go func() {
87-
if err := dry.dockerDaemon.StopContainer(id); err != nil {
88-
dry.appmessage(
89-
fmt.Sprintf("Error stopping container %s, err: %s", id, err.Error()))
140+
events := ui.EventSource{
141+
Events: h.eventChan,
142+
EventHandledCallback: func(e termbox.Event) error {
143+
return refreshScreen()
144+
},
145+
}
146+
prompt.OnFocus(events)
147+
conf, cancel := prompt.Text()
148+
h.setForwardEvents(false)
149+
widgets.remove(prompt)
150+
if cancel || (conf != "y" && conf != "Y") {
151+
152+
return
90153
}
154+
155+
dry.actionMessage(id, "Stopping")
156+
err := dry.dockerDaemon.StopContainer(id)
157+
if err == nil {
158+
widgets.ContainerMenu.ForContainer(id)
159+
refreshScreen()
160+
} else {
161+
dry.errorMessage(id, "stopping", err)
162+
}
163+
91164
}()
92165
case docker.LOGS:
93166
h.setForwardEvents(true)
94167
prompt := logsPrompt()
95-
dry.widgetRegistry.add(prompt)
168+
widgets.add(prompt)
96169
go func() {
97170
events := ui.EventSource{
98-
Events: h.eventChan,
171+
Events: h.events(),
99172
EventHandledCallback: func(e termbox.Event) error {
100173
return refreshScreen()
101174
},
102175
}
103176
prompt.OnFocus(events)
104-
dry.widgetRegistry.remove(prompt)
177+
widgets.remove(prompt)
105178
since, canceled := prompt.Text()
106179

107180
if canceled {
108-
h.setForwardEvents(false)
109181
return
110182
}
111183

112184
logs, err := h.dry.dockerDaemon.Logs(id, since)
113185
if err == nil {
114-
appui.Stream(logs, h.eventChan, func() {
115-
h.setForwardEvents(false)
116-
h.dry.SetViewMode(ContainerMenu)
117-
h.closeViewChan <- struct{}{}
118-
})
186+
appui.Stream(logs, h.eventChan,
187+
func() {
188+
h.dry.SetViewMode(ContainerMenu)
189+
f(h)
190+
h.setForwardEvents(false)
191+
refreshScreen()
192+
})
119193
} else {
120194
h.dry.appmessage("Error showing container logs: " + err.Error())
121-
h.setForwardEvents(false)
122195
}
123196
}()
124197
case docker.RM:
198+
prompt := appui.NewPrompt(
199+
fmt.Sprintf("Do you want to remove container %s? (y/N)", id))
200+
widgets.add(prompt)
201+
h.setForwardEvents(true)
202+
125203
go func() {
204+
events := ui.EventSource{
205+
Events: h.eventChan,
206+
EventHandledCallback: func(e termbox.Event) error {
207+
return refreshScreen()
208+
},
209+
}
210+
prompt.OnFocus(events)
211+
conf, cancel := prompt.Text()
212+
h.setForwardEvents(false)
213+
widgets.remove(prompt)
214+
if cancel || (conf != "y" && conf != "Y") {
215+
216+
return
217+
}
218+
126219
dry.actionMessage(id, "Removing")
127220
err := dry.dockerDaemon.Rm(id)
128221
if err == nil {
129222
dry.actionMessage(id, "removed")
223+
f(viewsToHandlers[Main])
224+
dry.SetViewMode(Main)
225+
refreshScreen()
130226
} else {
131227
dry.errorMessage(id, "removing", err)
132228
}
229+
133230
}()
134231

135232
case docker.STATS:
136-
137233
h.setForwardEvents(true)
234+
h.dry.SetViewMode(NoView)
138235
statsChan := dry.dockerDaemon.OpenChannel(container)
139236
go statsScreen(container, statsChan, screen, h.eventChan,
140237
func() {
141-
h.setForwardEvents(false)
142238
h.dry.SetViewMode(ContainerMenu)
143-
h.closeViewChan <- struct{}{}
239+
f(h)
240+
h.setForwardEvents(false)
241+
refreshScreen()
144242
})
145243

146244
case docker.INSPECT:
147-
h.setFocus(false)
148-
container, err := h.dry.dockerDaemon.Inspect(id)
149-
if err == nil {
150-
go func() {
151-
defer func() {
152-
h.setFocus(true)
153-
h.dry.SetViewMode(ContainerMenu)
154-
h.closeViewChan <- struct{}{}
155-
}()
156-
v, err := json.NewViewer(
157-
h.screen,
158-
appui.DryTheme,
159-
container)
160-
if err != nil {
161-
dry.appmessage(
162-
fmt.Sprintf("Error inspecting container: %s", err.Error()))
163-
return
164-
}
165-
v.Focus(h.eventChan)
166-
}()
245+
h.setForwardEvents(true)
246+
err := inspect(
247+
h.screen,
248+
h.eventChan,
249+
func(id string) (interface{}, error) {
250+
return h.dry.dockerDaemon.Inspect(id)
251+
},
252+
func() {
253+
h.dry.SetViewMode(ContainerMenu)
254+
f(h)
255+
h.setForwardEvents(false)
256+
refreshScreen()
257+
})(id)
258+
259+
if err != nil {
260+
dry.appmessage(
261+
fmt.Sprintf("Error inspecting container: %s", err.Error()))
262+
return
167263
}
168264
case docker.HISTORY:
169265
history, err := dry.dockerDaemon.History(container.ImageID)
170266

171267
if err == nil {
172268
renderer := appui.NewDockerImageHistoryRenderer(history)
173-
h.setFocus(false)
174-
go appui.Less(renderer, screen, h.eventChan, h.closeViewChan)
269+
270+
go appui.Less(renderer, screen, h.eventChan, func() {
271+
h.dry.SetViewMode(ContainerMenu)
272+
f(h)
273+
h.setForwardEvents(false)
274+
refreshScreen()
275+
})
175276
} else {
176277
dry.appmessage(
177278
fmt.Sprintf("Error showing image history: %s", err.Error()))

0 commit comments

Comments
 (0)