use redux store for managing state of underlying data

Signed-off-by: Jakub Sokołowski <jakub@status.im>
This commit is contained in:
Jakub Sokołowski 2019-06-27 14:21:51 -04:00 committed by Jakub
parent 51d299888d
commit d48daf6481
8 changed files with 169 additions and 68 deletions

View File

@ -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 {

18
keys.go
View File

@ -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)
//}

17
loop.go Normal file
View File

@ -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)
}
}

23
main.go
View File

@ -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)

38
model.go Normal file
View File

@ -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,
}
}

View File

@ -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 GenRenderFunc(g *gocui.Gui, state *State) func() {
return func() {
log.Printf("Rendering!")
ps := state.GetState()
renderPeers(g, ps.Peers)
renderPeerInfo(g, ps.Current)
}
}
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:
func renderPeers(g *gocui.Gui, peers []Peer) {
if len(peers) == 0 {
return
default:
peers := p.Fetch()
writePeers(g, peers)
writePeerDetails(g, p.selected)
}
<-time.After(interval * time.Second)
}
}
func (p *PeersState) Fetch() []Peer {
peers, err := p.c.getPeers()
if err != nil {
log.Panicln(err)
}
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 {

67
state.go Normal file
View File

@ -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
}

View File

@ -23,7 +23,7 @@ type ViewController struct {
SelFgColor gocui.Attribute
Keybindings []Binding
// extra field for view state
State interface{}
State *State
}
type ViewManager struct {