Files
p9/cmd/gui/state.go
Loki Verloren 0e2bba237a initial commit
2021-05-03 10:43:10 +02:00

320 lines
7.8 KiB
Go

package gui
import (
"crypto/cipher"
"encoding/json"
"io/ioutil"
"time"
"github.com/p9c/p9/pkg/amt"
"github.com/p9c/p9/pkg/btcaddr"
"github.com/p9c/p9/pkg/chaincfg"
uberatomic "go.uber.org/atomic"
l "github.com/p9c/p9/pkg/gel/gio/layout"
"github.com/p9c/p9/pkg/btcjson"
"github.com/p9c/p9/pkg/chainhash"
"github.com/p9c/p9/pkg/gcm"
"github.com/p9c/p9/pkg/transport"
"github.com/p9c/p9/pkg/util/atom"
)
const ZeroAddress = "1111111111111111111114oLvT2"
// CategoryFilter marks which transactions to omit from the filtered transaction list
type CategoryFilter struct {
Send bool
Generate bool
Immature bool
Receive bool
Unknown bool
}
func (c *CategoryFilter) Filter(s string) (include bool) {
include = true
if c.Send && s == "send" {
include = false
}
if c.Generate && s == "generate" {
include = false
}
if c.Immature && s == "immature" {
include = false
}
if c.Receive && s == "receive" {
include = false
}
if c.Unknown && s == "unknown" {
include = false
}
return
}
type AddressEntry struct {
Address string `json:"address"`
Message string `json:"message,omitempty"`
Label string `json:"label,omitempty"`
Amount amt.Amount `json:"amount"`
Created time.Time `json:"created"`
Modified time.Time `json:"modified"`
TxID string `json:txid,omitempty'`
}
type State struct {
lastUpdated *atom.Time
bestBlockHeight *atom.Int32
bestBlockHash *atom.Hash
balance *atom.Float64
balanceUnconfirmed *atom.Float64
goroutines []l.Widget
allTxs *atom.ListTransactionsResult
filteredTxs *atom.ListTransactionsResult
filter CategoryFilter
filterChanged *atom.Bool
currentReceivingAddress *atom.Address
isAddress *atom.Bool
activePage *uberatomic.String
sendAddresses []AddressEntry
receiveAddresses []AddressEntry
}
func GetNewState(params *chaincfg.Params, activePage *uberatomic.String) *State {
fc := &atom.Bool{
Bool: uberatomic.NewBool(false),
}
return &State{
lastUpdated: atom.NewTime(time.Now()),
bestBlockHeight: &atom.Int32{Int32: uberatomic.NewInt32(0)},
bestBlockHash: atom.NewHash(chainhash.Hash{}),
balance: &atom.Float64{Float64: uberatomic.NewFloat64(0)},
balanceUnconfirmed: &atom.Float64{
Float64: uberatomic.NewFloat64(0),
},
goroutines: nil,
allTxs: atom.NewListTransactionsResult(
[]btcjson.ListTransactionsResult{},
),
filteredTxs: atom.NewListTransactionsResult(
[]btcjson.ListTransactionsResult{},
),
filter: CategoryFilter{},
filterChanged: fc,
currentReceivingAddress: atom.NewAddress(
&btcaddr.PubKeyHash{},
params,
),
isAddress: &atom.Bool{Bool: uberatomic.NewBool(false)},
activePage: activePage,
}
}
func (s *State) BumpLastUpdated() {
s.lastUpdated.Store(time.Now())
}
func (s *State) SetReceivingAddress(addr btcaddr.Address) {
s.currentReceivingAddress.Store(addr)
}
func (s *State) IsReceivingAddress() bool {
addr := s.currentReceivingAddress.String.Load()
if addr == ZeroAddress || addr == "" {
s.isAddress.Store(false)
} else {
s.isAddress.Store(true)
}
return s.isAddress.Load()
}
// Save the state to the specified file
func (s *State) Save(filename string, pass []byte, debug bool) (e error) {
D.Ln("saving state...")
marshalled := s.Marshal()
var j []byte
if j, e = json.MarshalIndent(marshalled, "", " "); E.Chk(e) {
return
}
// D.Ln(string(j))
var ciph cipher.AEAD
if ciph, e = gcm.GetCipher(pass); E.Chk(e) {
return
}
var nonce []byte
if nonce, e = transport.GetNonce(ciph); E.Chk(e) {
return
}
crypted := append(nonce, ciph.Seal(nil, nonce, j, nil)...)
var b []byte
_ = b
if b, e = ciph.Open(nil, nonce, crypted[len(nonce):], nil); E.Chk(e) {
// since it was just created it should not fail to decrypt
panic(e)
// interrupt.Request()
return
}
if e = ioutil.WriteFile(filename, crypted, 0600); E.Chk(e) {
}
if debug {
if e = ioutil.WriteFile(filename+".clear", j, 0600); E.Chk(e) {
}
}
return
}
// Load in the configuration from the specified file and decrypt using the given password
func (s *State) Load(filename string, pass []byte) (e error) {
D.Ln("loading state...")
var data []byte
var ciph cipher.AEAD
if data, e = ioutil.ReadFile(filename); E.Chk(e) {
return
}
D.Ln("cipher:", string(pass))
if ciph, e = gcm.GetCipher(pass); E.Chk(e) {
return
}
ns := ciph.NonceSize()
D.Ln("nonce size:", ns)
nonce := data[:ns]
data = data[ns:]
var b []byte
if b, e = ciph.Open(nil, nonce, data, nil); E.Chk(e) {
// interrupt.Request()
return
}
// yay, right password, now unmarshal
ss := &Marshalled{}
if e = json.Unmarshal(b, ss); E.Chk(e) {
return
}
// D.Ln(string(b))
ss.Unmarshal(s)
return
}
type Marshalled struct {
LastUpdated time.Time
BestBlockHeight int32
BestBlockHash chainhash.Hash
Balance float64
BalanceUnconfirmed float64
AllTxs []btcjson.ListTransactionsResult
Filter CategoryFilter
ReceivingAddress string
ActivePage string
ReceiveAddressBook []AddressEntry
SendAddressBook []AddressEntry
}
func (s *State) Marshal() (out *Marshalled) {
out = &Marshalled{
LastUpdated: s.lastUpdated.Load(),
BestBlockHeight: s.bestBlockHeight.Load(),
BestBlockHash: s.bestBlockHash.Load(),
Balance: s.balance.Load(),
BalanceUnconfirmed: s.balanceUnconfirmed.Load(),
AllTxs: s.allTxs.Load(),
Filter: s.filter,
ReceivingAddress: s.currentReceivingAddress.Load().EncodeAddress(),
ActivePage: s.activePage.Load(),
ReceiveAddressBook: s.receiveAddresses,
SendAddressBook: s.sendAddresses,
}
return
}
func (m *Marshalled) Unmarshal(s *State) {
s.lastUpdated.Store(m.LastUpdated)
s.bestBlockHeight.Store(m.BestBlockHeight)
s.bestBlockHash.Store(m.BestBlockHash)
s.balance.Store(m.Balance)
s.balanceUnconfirmed.Store(m.BalanceUnconfirmed)
if len(s.allTxs.Load()) < len(m.AllTxs) {
s.allTxs.Store(m.AllTxs)
}
s.receiveAddresses = m.ReceiveAddressBook
s.sendAddresses = m.SendAddressBook
s.filter = m.Filter
if m.ReceivingAddress != "1111111111111111111114oLvT2" {
var e error
var ra btcaddr.Address
if ra, e = btcaddr.Decode(m.ReceivingAddress, s.currentReceivingAddress.ForNet); E.Chk(e) {
}
s.currentReceivingAddress.Store(ra)
}
s.SetActivePage(m.ActivePage)
return
}
func (s *State) Goroutines() []l.Widget {
return s.goroutines
}
func (s *State) SetGoroutines(gr []l.Widget) {
s.goroutines = gr
}
func (s *State) SetAllTxs(atxs []btcjson.ListTransactionsResult) {
s.allTxs.Store(atxs)
// generate filtered state
filteredTxs := make([]btcjson.ListTransactionsResult, 0, len(s.allTxs.Load()))
for i := range atxs {
if s.filter.Filter(atxs[i].Category) {
filteredTxs = append(filteredTxs, atxs[i])
}
}
s.filteredTxs.Store(filteredTxs)
}
func (s *State) LastUpdated() time.Time {
return s.lastUpdated.Load()
}
func (s *State) BestBlockHeight() int32 {
return s.bestBlockHeight.Load()
}
func (s *State) BestBlockHash() *chainhash.Hash {
o := s.bestBlockHash.Load()
return &o
}
func (s *State) Balance() float64 {
return s.balance.Load()
}
func (s *State) BalanceUnconfirmed() float64 {
return s.balanceUnconfirmed.Load()
}
func (s *State) ActivePage() string {
return s.activePage.Load()
}
func (s *State) SetActivePage(page string) {
s.activePage.Store(page)
}
func (s *State) SetBestBlockHeight(height int32) {
s.BumpLastUpdated()
s.bestBlockHeight.Store(height)
}
func (s *State) SetBestBlockHash(h *chainhash.Hash) {
s.BumpLastUpdated()
s.bestBlockHash.Store(*h)
}
func (s *State) SetBalance(total float64) {
s.BumpLastUpdated()
s.balance.Store(total)
}
func (s *State) SetBalanceUnconfirmed(unconfirmed float64) {
s.BumpLastUpdated()
s.balanceUnconfirmed.Store(unconfirmed)
}