272 lines
6.7 KiB
Go
272 lines
6.7 KiB
Go
|
package deal
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"time"
|
||
|
|
||
|
tm "github.com/buger/goterm"
|
||
|
"github.com/fatih/color"
|
||
|
"github.com/filecoin-project/go-address"
|
||
|
"github.com/filecoin-project/go-fil-markets/storagemarket"
|
||
|
"github.com/filecoin-project/go-state-types/abi"
|
||
|
lapi "github.com/filecoin-project/lotus/api"
|
||
|
"github.com/filecoin-project/lotus/lib/tablewriter"
|
||
|
"github.com/ipfs/go-cid"
|
||
|
term "github.com/nsf/termbox-go"
|
||
|
)
|
||
|
|
||
|
var mockDealInfos []lapi.DealInfo
|
||
|
|
||
|
func init() {
|
||
|
dummyCid, err := cid.Parse("bafkqaaa")
|
||
|
if err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
|
||
|
mockDealStages := storagemarket.DealStages{
|
||
|
At: time.Now(),
|
||
|
Stages: []storagemarket.DealStage{{
|
||
|
Name: "Reserving Funds",
|
||
|
ExpectedDuration: "5 epochs",
|
||
|
StartedAt: time.Now().Add(-5 * time.Minute),
|
||
|
Logs: []string{
|
||
|
"Sending AddBalance message for 0.3FIL",
|
||
|
"Waiting 5 epochs (2:30) for confirmation",
|
||
|
"Funds for deal reserved successfully",
|
||
|
},
|
||
|
}, {
|
||
|
Name: "Sending Deal Proposal to Provider",
|
||
|
ExpectedDuration: "5 epochs",
|
||
|
StartedAt: time.Now().Add(-2 * time.Minute),
|
||
|
Logs: []string{
|
||
|
"Proposal: 512MB for 300 days @ 0.1FIL / GB / epoch",
|
||
|
"Deal proposal accepted by provider",
|
||
|
"Funds for deal reserved successfully",
|
||
|
},
|
||
|
}, {
|
||
|
Name: "Sending deal data to Provider",
|
||
|
ExpectedDuration: "13 minutes",
|
||
|
StartedAt: time.Now().Add(-53 * time.Second),
|
||
|
Logs: []string{
|
||
|
"Progress: 4:02 254MB / 1024MB (1MB / sec)",
|
||
|
"Connection to Provider f01234 disconnected, retrying in 8s",
|
||
|
},
|
||
|
}, {
|
||
|
Name: "Waiting for deal to be published by Provider",
|
||
|
ExpectedDuration: "several hours",
|
||
|
}, {
|
||
|
Name: "Waiting for pre-commit message from Provider",
|
||
|
ExpectedDuration: "several hours",
|
||
|
}, {
|
||
|
Name: "Waiting for prove-commit message from Provider",
|
||
|
ExpectedDuration: "several hours",
|
||
|
}},
|
||
|
}
|
||
|
|
||
|
mockDealInfos = []lapi.DealInfo{{
|
||
|
State: 3,
|
||
|
Message: "Reserving Client Funds",
|
||
|
DealStages: mockDealStages,
|
||
|
Provider: address.TestAddress,
|
||
|
DataRef: nil,
|
||
|
PieceCID: dummyCid,
|
||
|
Size: 512 * 1024 * 1024,
|
||
|
PricePerEpoch: abi.NewTokenAmount(10),
|
||
|
Duration: 300,
|
||
|
DealID: 10,
|
||
|
CreationTime: time.Now().Add(-5 * time.Second),
|
||
|
Verified: false,
|
||
|
TransferChannelID: nil,
|
||
|
DataTransfer: nil,
|
||
|
}, {
|
||
|
State: 5,
|
||
|
Message: "Publishing Deal",
|
||
|
DealStages: mockDealStages,
|
||
|
Provider: address.TestAddress,
|
||
|
DataRef: nil,
|
||
|
PieceCID: dummyCid,
|
||
|
Size: 482 * 1024 * 1024,
|
||
|
PricePerEpoch: abi.NewTokenAmount(12),
|
||
|
Duration: 323,
|
||
|
DealID: 14,
|
||
|
CreationTime: time.Now().Add(-23 * time.Minute),
|
||
|
Verified: false,
|
||
|
TransferChannelID: nil,
|
||
|
DataTransfer: nil,
|
||
|
}, {
|
||
|
State: 7,
|
||
|
Message: "Waiting for Pre-Commit",
|
||
|
DealStages: mockDealStages,
|
||
|
Provider: address.TestAddress,
|
||
|
DataRef: nil,
|
||
|
PieceCID: dummyCid,
|
||
|
Size: 2 * 1024 * 1024 * 1024,
|
||
|
PricePerEpoch: abi.NewTokenAmount(8),
|
||
|
Duration: 298,
|
||
|
DealID: 8,
|
||
|
CreationTime: time.Now().Add(-3 * time.Hour),
|
||
|
Verified: false,
|
||
|
TransferChannelID: nil,
|
||
|
DataTransfer: nil,
|
||
|
}, {
|
||
|
State: 2,
|
||
|
Message: "Transferring Data",
|
||
|
DealStages: mockDealStages,
|
||
|
Provider: address.TestAddress,
|
||
|
DataRef: nil,
|
||
|
PieceCID: dummyCid,
|
||
|
Size: 23 * 1024 * 1024,
|
||
|
PricePerEpoch: abi.NewTokenAmount(11),
|
||
|
Duration: 328,
|
||
|
DealID: 3,
|
||
|
CreationTime: time.Now().Add(-49 * time.Hour),
|
||
|
Verified: false,
|
||
|
TransferChannelID: nil,
|
||
|
DataTransfer: nil,
|
||
|
}}
|
||
|
}
|
||
|
|
||
|
func ShowDealsCmd(ctx context.Context, api lapi.FullNode) error {
|
||
|
ctx, cancel := context.WithCancel(ctx)
|
||
|
defer cancel()
|
||
|
|
||
|
//localDeals, err := api.ClientListDeals(ctx)
|
||
|
//if err != nil {
|
||
|
// return err
|
||
|
//}
|
||
|
localDeals := mockDealInfos
|
||
|
|
||
|
return showDealsUX(ctx, localDeals)
|
||
|
}
|
||
|
|
||
|
func showDealsUX(ctx context.Context, deals []lapi.DealInfo) error {
|
||
|
err := term.Init()
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
defer term.Close()
|
||
|
|
||
|
renderer := dealRenderer{out: tm.Screen}
|
||
|
|
||
|
dealIdx := -1
|
||
|
state := "main"
|
||
|
highlighted := -1
|
||
|
for {
|
||
|
if err := ctx.Err(); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
switch state {
|
||
|
case "main":
|
||
|
renderMain := func(hlite int) error {
|
||
|
tm.Clear()
|
||
|
tm.MoveCursor(1, 1)
|
||
|
err := renderer.renderDeals(deals, hlite)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
tm.Flush()
|
||
|
return nil
|
||
|
}
|
||
|
err := renderMain(highlighted)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
switch ev := term.PollEvent(); ev.Type {
|
||
|
case term.EventKey:
|
||
|
switch {
|
||
|
case ev.Ch == 'q', ev.Key == term.KeyEsc:
|
||
|
return nil
|
||
|
case ev.Key == term.KeyArrowUp:
|
||
|
term.Sync()
|
||
|
if highlighted > 0 {
|
||
|
highlighted--
|
||
|
}
|
||
|
case ev.Key == term.KeyArrowDown:
|
||
|
term.Sync()
|
||
|
highlighted++
|
||
|
case ev.Key == term.KeyEnter:
|
||
|
term.Sync()
|
||
|
dealIdx = highlighted
|
||
|
state = "deal"
|
||
|
}
|
||
|
case term.EventError:
|
||
|
return ev.Err
|
||
|
}
|
||
|
case "deal":
|
||
|
tm.Clear()
|
||
|
tm.MoveCursor(1, 1)
|
||
|
renderer.renderDeal(deals[dealIdx])
|
||
|
tm.Flush()
|
||
|
|
||
|
switch ev := term.PollEvent(); ev.Type {
|
||
|
case term.EventKey:
|
||
|
if ev.Ch == 'q' || ev.Key == term.KeyEsc || ev.Key == term.KeyEnter || ev.Key == term.KeyArrowLeft {
|
||
|
term.Sync()
|
||
|
state = "main"
|
||
|
}
|
||
|
case term.EventError:
|
||
|
return ev.Err
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type dealRenderer struct {
|
||
|
out io.Writer
|
||
|
}
|
||
|
|
||
|
func (r *dealRenderer) renderDeals(deals []lapi.DealInfo, highlighted int) error {
|
||
|
tw := tablewriter.New(
|
||
|
tablewriter.Col(""),
|
||
|
tablewriter.Col("Created"),
|
||
|
tablewriter.Col("Provider"),
|
||
|
tablewriter.Col("Size"),
|
||
|
tablewriter.Col("State"),
|
||
|
)
|
||
|
for i, di := range deals {
|
||
|
lineNum := fmt.Sprintf("%d", i+1)
|
||
|
cols := map[string]interface{}{
|
||
|
"": lineNum,
|
||
|
"Created": time.Since(di.CreationTime).Round(time.Second),
|
||
|
"Provider": di.Provider,
|
||
|
"Size": di.Size,
|
||
|
"State": di.Message,
|
||
|
}
|
||
|
if i == highlighted {
|
||
|
for k, v := range cols {
|
||
|
cols[k] = color.YellowString(fmt.Sprint(v))
|
||
|
}
|
||
|
}
|
||
|
tw.Write(cols)
|
||
|
}
|
||
|
return tw.Flush(r.out)
|
||
|
}
|
||
|
|
||
|
func (r *dealRenderer) renderDeal(di lapi.DealInfo) error {
|
||
|
_, err := fmt.Fprintf(r.out, "Deal %d\n", di.DealID)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
for _, stg := range di.DealStages.Stages {
|
||
|
msg := fmt.Sprintf("%s (%s)", stg.Name, stg.ExpectedDuration)
|
||
|
if stg.StartedAt.IsZero() {
|
||
|
msg = color.YellowString(msg)
|
||
|
}
|
||
|
_, err := fmt.Fprintf(r.out, "%s\n", msg)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
for _, l := range stg.Logs {
|
||
|
_, err = fmt.Fprintf(r.out, " %s\n", l)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return nil
|
||
|
}
|