From d48daf6481ff2f2d4ced0f9a693d18dd95605754 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Soko=C5=82owski?= Date: Thu, 27 Jun 2019 14:21:51 -0400 Subject: [PATCH] use redux store for managing state of underlying data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jakub SokoĊ‚owski --- client.go | 10 +++---- keys.go | 18 ++++++------ loop.go | 17 +++++++++++ main.go | 23 +++++++++++---- model.go | 38 ++++++++++++++++++++++++ peers.go => render.go | 62 +++++++++------------------------------ state.go | 67 +++++++++++++++++++++++++++++++++++++++++++ view.go | 2 +- 8 files changed, 169 insertions(+), 68 deletions(-) create mode 100644 loop.go create mode 100644 model.go rename peers.go => render.go (56%) create mode 100644 state.go diff --git a/client.go b/client.go index 60112d6..202d7b0 100644 --- a/client.go +++ b/client.go @@ -4,19 +4,19 @@ import ( "github.com/ethereum/go-ethereum/rpc" ) -type client struct { +type StatusGoClient struct { rpcClient *rpc.Client } -func newClient(url string) (*client, error) { +func newClient(url string) (*StatusGoClient, error) { rpcClient, err := rpc.Dial(url) if err != nil { return nil, err } - return &client{rpcClient}, nil + return &StatusGoClient{rpcClient}, nil } -func (c *client) getPeers() ([]Peer, error) { +func (c *StatusGoClient) getPeers() ([]Peer, error) { peers := make([]Peer, 0) err := c.rpcClient.Call(&peers, "admin_peers") if err != nil { @@ -25,7 +25,7 @@ func (c *client) getPeers() ([]Peer, error) { return peers, nil } -func (c *client) removePeer(enode string) (bool, error) { +func (c *StatusGoClient) removePeer(enode string) (bool, error) { var rval bool err := c.rpcClient.Call(&rval, "admin_removePeer", enode) if err != nil { diff --git a/keys.go b/keys.go index 1a88823..3595638 100644 --- a/keys.go +++ b/keys.go @@ -24,15 +24,15 @@ func MoveCursor(mod int, vc *ViewController, g *gocui.Gui, v *gocui.View) error } cx, cy := v.Cursor() // get peers - ps := vc.State.(*PeersState) - peers := ps.list + ps := vc.State.GetState() + peers := ps.Peers // Don't go beyond available list of peers if cy+mod >= len(peers) || cy+mod < 0 { return nil } // update currently selected peer in the list - ps.selected = &peers[cy+mod] - writePeerDetails(g, ps.selected) + current := &peers[cy+mod] + vc.State.SetCurrent(current) if err := v.SetCursor(cx, cy+mod); err != nil { if mod == -1 { return nil @@ -45,8 +45,8 @@ func MoveCursor(mod int, vc *ViewController, g *gocui.Gui, v *gocui.View) error return nil } -func (vc *ViewController) HandleDelete(g *gocui.Gui, v *gocui.View) error { - ps := vc.State.(*PeersState) - selectedPeer := ps.selected - rval := ps.Remove(*selectedPeer) -} +//func (vc *ViewController) HandleDelete(g *gocui.Gui, v *gocui.View) error { +// ps := vc.State.GetState() +// currentPeer := ps.Current +// return vc.State.Remove(vc.client, *currentPeer) +//} diff --git a/loop.go b/loop.go new file mode 100644 index 0000000..87e999f --- /dev/null +++ b/loop.go @@ -0,0 +1,17 @@ +package main + +import ( + "time" +) + +func FetchLoop(client *StatusGoClient, state *State) { + for { + select { + case <-threadDone: + return + default: + state.Fetch(client) + } + <-time.After(interval * time.Second) + } +} diff --git a/main.go b/main.go index dd0893f..13a2323 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "log" "os" @@ -16,19 +17,30 @@ const interval = 5 var threadDone = make(chan struct{}) func main() { + // Custom location for log messages clientLogFile, err := os.OpenFile("./app.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644) if err != nil { log.Panicln(err) } log.SetOutput(clientLogFile) + // Core object for the Terminal UI g, err := gocui.NewGui(gocui.OutputNormal) if err != nil { log.Panicln(err) } defer g.Close() - peers := NewPeersState(host, port) + // Client necessary for doing RPC calls to status-go + url := fmt.Sprintf("http://%s:%d", host, port) + client, err := newClient(url) + if err != nil { + log.Panicln(err) + } + + state := NewState() + // Subscribe rendering method to state changes + state.Store.Subscribe(GenRenderFunc(g, state)) mainView := &ViewController{ Name: "main", @@ -40,7 +52,7 @@ func main() { Current: true, SelFgColor: gocui.ColorBlack, SelBgColor: gocui.ColorGreen, - State: peers, + State: state, // corner positions TopLeft: func(mx, my int) (int, int) { return 0, 0 }, BotRight: func(mx, my int) (int, int) { return mx - 1, my / 2 }, @@ -52,8 +64,8 @@ func main() { Binding{gocui.KeyArrowDown, gocui.ModNone, mainView.CursorDown}, Binding{'k', gocui.ModNone, mainView.CursorUp}, Binding{'j', gocui.ModNone, mainView.CursorDown}, - Binding{gocui.KeyDelete, gocui.ModNone, mainView.HandleDelete}, - Binding{'d', gocui.ModNone, mainView.HandleDelete}, + //Binding{gocui.KeyDelete, gocui.ModNone, mainView.HandleDelete}, + //Binding{'d', gocui.ModNone, mainView.HandleDelete}, } infoView := &ViewController{ Name: "info", @@ -61,6 +73,7 @@ func main() { Placeholder: "Loading details...", Enabled: true, Wrap: true, + State: state, // corner positions TopLeft: func(mx, my int) (int, int) { return 0, (my / 2) + 1 }, BotRight: func(mx, my int) (int, int) { return mx - 1, my - 1 }, @@ -73,7 +86,7 @@ func main() { g.SetManagerFunc(vm.Layout) // Start RPC calling routine - go peers.FetchLoop(g) + go FetchLoop(client, state) if err := g.MainLoop(); err != nil && err != gocui.ErrQuit { log.Panicln(err) diff --git a/model.go b/model.go new file mode 100644 index 0000000..c45c412 --- /dev/null +++ b/model.go @@ -0,0 +1,38 @@ +package main + +import ( + "github.com/dannypsnl/redux/rematch" +) + +type PeersState struct { + Peers []Peer + Current *Peer +} + +type Model struct { + rematch.Reducer + State PeersState +} + +type Todo struct { + Title string + Done bool +} + +func (todo *Model) Current(state PeersState, peer *Peer) PeersState { + return PeersState{ + Peers: state.Peers, + Current: peer, + } +} + +func (todo *Model) Update(state PeersState, peers []Peer) PeersState { + current := state.Current + if state.Current == nil { + current = &peers[0] + } + return PeersState{ + Peers: peers, + Current: current, + } +} diff --git a/peers.go b/render.go similarity index 56% rename from peers.go rename to render.go index 086f27c..6b205fc 100644 --- a/peers.go +++ b/render.go @@ -2,54 +2,25 @@ package main import ( "fmt" - "github.com/jroimartin/gocui" "log" "strings" - "time" + + "github.com/jroimartin/gocui" ) -type PeersState struct { - c *client - list []Peer - selected *Peer -} - -func NewPeersState(host string, port int) *PeersState { - url := fmt.Sprintf("http://%s:%d", host, port) - c, err := newClient(url) - if err != nil { - log.Panicln(err) - } - return &PeersState{c: c} -} - -func (p *PeersState) FetchLoop(g *gocui.Gui) { - for { - select { - case <-threadDone: - return - default: - peers := p.Fetch() - writePeers(g, peers) - writePeerDetails(g, p.selected) - } - <-time.After(interval * time.Second) +func GenRenderFunc(g *gocui.Gui, state *State) func() { + return func() { + log.Printf("Rendering!") + ps := state.GetState() + renderPeers(g, ps.Peers) + renderPeerInfo(g, ps.Current) } } -func (p *PeersState) Fetch() []Peer { - peers, err := p.c.getPeers() - if err != nil { - log.Panicln(err) +func renderPeers(g *gocui.Gui, peers []Peer) { + if len(peers) == 0 { + return } - p.list = peers - if p.selected == nil { - p.selected = &peers[0] - } - return peers -} - -func writePeers(g *gocui.Gui, peers []Peer) { g.Update(func(g *gocui.Gui) error { v, err := g.View("main") if err != nil { @@ -64,15 +35,10 @@ func writePeers(g *gocui.Gui, peers []Peer) { }) } -func (p *PeersState) Remove(peer Peer) error { - success, err := p.c.removePeer(peer.Enode) - if err != nil || success != true { - log.Panicln(err) +func renderPeerInfo(g *gocui.Gui, peer *Peer) { + if peer == nil { + return } - return nil -} - -func writePeerDetails(g *gocui.Gui, peer *Peer) { g.Update(func(g *gocui.Gui) error { v, err := g.View("info") if err != nil { diff --git a/state.go b/state.go new file mode 100644 index 0000000..a90a68b --- /dev/null +++ b/state.go @@ -0,0 +1,67 @@ +package main + +import ( + "log" + + _ "github.com/dannypsnl/redux" + "github.com/dannypsnl/redux/rematch" + "github.com/dannypsnl/redux/store" +) + +type State struct { + Reducer *Model + Store *store.Store + updatePeers *rematch.Action + setCurrent *rematch.Action +} + +func NewState() *State { + // Generate the reducer from our model + Reducer := &Model{ + State: PeersState{ + Peers: make([]Peer, 0), + Current: nil, + }, + } + // Instantiate the redux state from the reducer + return &State{ + Reducer: Reducer, + // Define the store + Store: store.New(Reducer), + // Define available reducers for the store + updatePeers: Reducer.Action(Reducer.Update), + setCurrent: Reducer.Action(Reducer.Current), + } +} + +// Helpers for shorter calls +func (s *State) Update(peers []Peer) { + s.Store.Dispatch(s.updatePeers.With(peers)) +} +func (s *State) SetCurrent(peer *Peer) { + s.Store.Dispatch(s.setCurrent.With(peer)) +} +func (s *State) GetState() PeersState { + return s.Store.StateOf(s.Reducer).(PeersState) +} +func (s *State) Fetch(client *StatusGoClient) { + peers, err := client.getPeers() + if err != nil { + log.Panicln(err) + } + log.Printf("peers: %v\n", peers) + ps := s.GetState() + s.Update(peers) + if ps.Current == nil { + s.SetCurrent(&peers[0]) + } +} + +func (s *State) Remove(client *StatusGoClient, peer Peer) error { + success, err := client.removePeer(peer.Enode) + if err != nil || success != true { + log.Panicln(err) + } + s.Fetch(client) + return nil +} diff --git a/view.go b/view.go index d660fc9..0124fd7 100644 --- a/view.go +++ b/view.go @@ -23,7 +23,7 @@ type ViewController struct { SelFgColor gocui.Attribute Keybindings []Binding // extra field for view state - State interface{} + State *State } type ViewManager struct {