pull/38/head
Miguel Mota 5 years ago
parent e457a89755
commit 0eb3daed6f

2
.gitignore vendored

@ -29,6 +29,8 @@ bfg.jar
build build
dist dist
bin bin
main
!main.go
# flatpak # flatpak
.flatpak-builder .flatpak-builder

@ -4,6 +4,17 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.1.4] - 2019-04-21
### Changed
- CoinMarketCap legacy V2 API to Pro V1 API
### Added
- Config option to use CoinMarketCap Pro V1 API KEY
## [1.1.3] - 2019-02-25
### Fixed
- Vendor dependencies
## [1.1.2] - 2018-12-30 ## [1.1.2] - 2018-12-30
### Fixed ### Fixed
- Paginate CoinMarketCap V1 API responses due to their backward-incompatible update - Paginate CoinMarketCap V1 API responses due to their backward-incompatible update

@ -10,6 +10,9 @@ deps:
debug: debug:
DEBUG=1 go run main.go DEBUG=1 go run main.go
build:
@go build main.go
# http://macappstore.org/upx # http://macappstore.org/upx
build/mac: clean/mac build/mac: clean/mac
env GOARCH=amd64 go build -ldflags "-s -w" -o bin/macos/cointop && upx bin/macos/cointop env GOARCH=amd64 go build -ldflags "-s -w" -o bin/macos/cointop && upx bin/macos/cointop

@ -354,7 +354,7 @@ You can then configure the actions you want for each key:
```toml ```toml
currency = "USD" currency = "USD"
defaultView = "default" defaultView = ""
[shortcuts] [shortcuts]
"$" = "last_page" "$" = "last_page"
@ -422,6 +422,13 @@ defaultView = "default"
t = "sort_column_total_supply" t = "sort_column_total_supply"
u = "sort_column_last_updated" u = "sort_column_last_updated"
v = "sort_column_24h_volume" v = "sort_column_24h_volume"
[favorites]
[portfolio]
[coinmarketcap]
pro_api_key = ""
``` ```
You may specify a different config file to use by using the `-config` flag: You may specify a different config file to use by using the `-config` flag:
@ -519,7 +526,14 @@ Frequently asked questions:
- Q: How do I add my CoinMarketCap Pro API Key? - Q: How do I add my CoinMarketCap Pro API Key?
- A: Export the environment variable `CMC_PRO_API_KEY` containing the API key. - A: Add the API key in the cointop config file:
```toml
[coinmarketcap]
pro_api_key = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
```
Alternatively, you can export the environment variable `CMC_PRO_API_KEY` containing the API key.
- Q: I installed cointop without errors but the command is not found. - Q: I installed cointop without errors but the command is not found.

@ -3,7 +3,6 @@ package cointop
import ( import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log"
"os" "os"
"strings" "strings"
"sync" "sync"
@ -16,6 +15,7 @@ import (
"github.com/miguelmota/cointop/cointop/common/filecache" "github.com/miguelmota/cointop/cointop/common/filecache"
"github.com/miguelmota/cointop/cointop/common/table" "github.com/miguelmota/cointop/cointop/common/table"
"github.com/patrickmn/go-cache" "github.com/patrickmn/go-cache"
log "github.com/sirupsen/logrus"
) )
// TODO: clean up and optimize codebase // TODO: clean up and optimize codebase
@ -82,6 +82,8 @@ type Cointop struct {
inputview *gocui.View inputview *gocui.View
inputviewname string inputviewname string
defaultView string defaultView string
apiKeys *apiKeys
limiter <-chan time.Time
} }
// PortfolioEntry is portfolio entry // PortfolioEntry is portfolio entry
@ -100,6 +102,11 @@ type Config struct {
ConfigFilepath string ConfigFilepath string
} }
// apiKeys is api keys structure
type apiKeys struct {
cmc string
}
var defaultConfigPath = "~/.cointop/config" var defaultConfigPath = "~/.cointop/config"
// NewCointop initializes cointop // NewCointop initializes cointop
@ -117,18 +124,19 @@ func NewCointop(config *Config) *Cointop {
} }
ct := &Cointop{ ct := &Cointop{
api: api.NewCMC(), allcoinsslugmap: make(map[string]*coin),
refreshticker: time.NewTicker(1 * time.Minute), allcoins: []*coin{},
sortby: "rank", refreshticker: time.NewTicker(1 * time.Minute),
page: 0, sortby: "rank",
perpage: 100, page: 0,
forcerefresh: make(chan bool), perpage: 100,
maxtablewidth: 175, forcerefresh: make(chan bool),
actionsmap: actionsMap(), maxtablewidth: 175,
shortcutkeys: defaultShortcuts(), actionsmap: actionsMap(),
shortcutkeys: defaultShortcuts(),
// DEPRECATED: favorites by 'symbol' is deprecated because of collisions. Kept for backward compatibility. // DEPRECATED: favorites by 'symbol' is deprecated because of collisions. Kept for backward compatibility.
favoritesbysymbol: map[string]bool{}, favoritesbysymbol: make(map[string]bool),
favorites: map[string]bool{}, favorites: make(map[string]bool),
cache: cache.New(1*time.Minute, 2*time.Minute), cache: cache.New(1*time.Minute, 2*time.Minute),
debug: debug, debug: debug,
configFilepath: configFilepath, configFilepath: configFilepath,
@ -189,21 +197,36 @@ func NewCointop(config *Config) *Cointop {
}, },
portfolioupdatemenuviewname: "portfolioupdatemenu", portfolioupdatemenuviewname: "portfolioupdatemenu",
inputviewname: "input", inputviewname: "input",
apiKeys: new(apiKeys),
limiter: time.Tick(2 * time.Second),
} }
err := ct.setupConfig() err := ct.setupConfig()
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
allcoinsslugmap := map[string]types.Coin{} ct.api = api.NewCMC(ct.apiKeys.cmc)
coinscachekey := "allcoinsslugmap" coinscachekey := "allcoinsslugmap"
filecache.Get(coinscachekey, &allcoinsslugmap) filecache.Get(coinscachekey, &ct.allcoinsslugmap)
ct.cache.Set(coinscachekey, allcoinsslugmap, 10*time.Second)
for k := range ct.allcoinsslugmap {
ct.allcoins = append(ct.allcoins, ct.allcoinsslugmap[k])
}
if len(ct.allcoins) > 1 {
max := len(ct.allcoins)
if max > 100 {
max = 100
}
ct.sort(ct.sortby, ct.sortdesc, ct.allcoins, false)
ct.coins = ct.allcoins[0:max]
}
// DEPRECATED: favorites by 'symbol' is deprecated because of collisions. Kept for backward compatibility. // DEPRECATED: favorites by 'symbol' is deprecated because of collisions. Kept for backward compatibility.
// Here we're doing a lookup based on symbol and setting the favorite to the coin name instead of coin symbol. // Here we're doing a lookup based on symbol and setting the favorite to the coin name instead of coin symbol.
for i := range allcoinsslugmap { for i := range ct.allcoinsslugmap {
coin := allcoinsslugmap[i] coin := ct.allcoinsslugmap[i]
for k := range ct.favoritesbysymbol { for k := range ct.favoritesbysymbol {
if coin.Symbol == k { if coin.Symbol == k {
ct.favorites[coin.Name] = true ct.favorites[coin.Name] = true

@ -5,8 +5,8 @@ import (
) )
// NewCMC new CoinMarketCap API // NewCMC new CoinMarketCap API
func NewCMC() Interface { func NewCMC(apiKey string) Interface {
return cmc.New() return cmc.New(apiKey)
} }
// NewCC new CryptoCompare API // NewCC new CryptoCompare API

@ -1,12 +1,10 @@
package coinmarketcap package coinmarketcap
import ( import (
"errors"
"fmt" "fmt"
"os" "os"
"strconv" "strconv"
"strings" "strings"
"sync"
"time" "time"
apitypes "github.com/miguelmota/cointop/cointop/common/api/types" apitypes "github.com/miguelmota/cointop/cointop/common/api/types"
@ -20,9 +18,12 @@ type Service struct {
} }
// New new service // New new service
func New() *Service { func New(apiKey string) *Service {
if apiKey == "" {
apiKey = os.Getenv("CMC_PRO_API_KEY")
}
client := cmc.NewClient(&cmc.Config{ client := cmc.NewClient(&cmc.Config{
ProAPIKey: os.Getenv("CMC_PRO_API_KEY"), ProAPIKey: apiKey,
}) })
return &Service{ return &Service{
client: client, client: client,
@ -31,15 +32,18 @@ func New() *Service {
// Ping ping API // Ping ping API
func (s *Service) Ping() error { func (s *Service) Ping() error {
info, err := s.client.Cryptocurrency.Info(&cmc.InfoOptions{ // TODO: notify in statusbar of failed ping (instead of fatal to make it work offline)
Symbol: "BTC", /*
}) info, err := s.client.Cryptocurrency.Info(&cmc.InfoOptions{
if err != nil { Symbol: "BTC",
return errors.New("failed to ping") })
} if err != nil {
if info == nil { return errors.New("failed to ping")
return errors.New("failed to ping") }
} if info == nil {
return errors.New("failed to ping")
}
*/
return nil return nil
} }
@ -81,33 +85,26 @@ func (s *Service) getLimitedCoinData(convert string, offset int) (map[string]api
} }
// GetAllCoinData gets all coin data. Need to paginate through all pages // GetAllCoinData gets all coin data. Need to paginate through all pages
func (s *Service) GetAllCoinData(convert string) (chan map[string]apitypes.Coin, error) { func (s *Service) GetAllCoinData(convert string, ch chan map[string]apitypes.Coin) error {
var wg sync.WaitGroup
ch := make(chan map[string]apitypes.Coin)
go func() { go func() {
var mutex sync.Mutex maxPages := 10
maxPages := 15 defer close(ch)
for i := 0; i < maxPages; i++ { for i := 0; i < maxPages; i++ {
time.Sleep(time.Duration(i) * time.Second) if i > 0 {
wg.Add(1) time.Sleep(1 * time.Second)
go func(j int) { }
defer wg.Done() coins, err := s.getLimitedCoinData(convert, i)
coins, err := s.getLimitedCoinData(convert, j) if err != nil {
if err != nil { return
return }
} ret := make(map[string]apitypes.Coin)
mutex.Lock() for k, v := range coins {
defer mutex.Unlock() ret[k] = v
ret := make(map[string]apitypes.Coin) }
for k, v := range coins { ch <- ret
ret[k] = v
}
ch <- ret
}(i)
} }
wg.Wait()
}() }()
return ch, nil return nil
} }
// GetCoinGraphData gets coin graph data // GetCoinGraphData gets coin graph data

@ -7,7 +7,7 @@ import (
// Interface interface // Interface interface
type Interface interface { type Interface interface {
Ping() error Ping() error
GetAllCoinData(convert string) (chan map[string]types.Coin, error) GetAllCoinData(convert string, ch chan map[string]types.Coin) error
GetCoinGraphData(coin string, start int64, end int64) (types.CoinGraph, error) GetCoinGraphData(coin string, start int64, end int64) (types.CoinGraph, error)
GetGlobalMarketGraphData(start int64, end int64) (types.MarketGraph, error) GetGlobalMarketGraphData(start int64, end int64) (types.MarketGraph, error)
GetGlobalMarketData(convert string) (types.GlobalMarketData, error) GetGlobalMarketData(convert string) (types.GlobalMarketData, error)

@ -12,11 +12,12 @@ import (
var fileperm = os.FileMode(0644) var fileperm = os.FileMode(0644)
type config struct { type config struct {
Shortcuts map[string]interface{} `toml:"shortcuts"` Shortcuts map[string]interface{} `toml:"shortcuts"`
Favorites map[string][]interface{} `toml:"favorites"` Favorites map[string][]interface{} `toml:"favorites"`
Portfolio map[string]interface{} `toml:"portfolio"` Portfolio map[string]interface{} `toml:"portfolio"`
Currency interface{} `toml:"currency"` Currency interface{} `toml:"currency"`
DefaultView interface{} `toml:"defaultView"` DefaultView interface{} `toml:"defaultView"`
CoinMarketCap map[string]interface{} `toml:"coinmarketcap"`
} }
func (ct *Cointop) setupConfig() error { func (ct *Cointop) setupConfig() error {
@ -48,6 +49,10 @@ func (ct *Cointop) setupConfig() error {
if err != nil { if err != nil {
return err return err
} }
err = ct.loadAPIKeysFromConfig()
if err != nil {
return err
}
return nil return nil
} }
@ -162,13 +167,17 @@ func (ct *Cointop) configToToml() ([]byte, error) {
var currencyIfc interface{} = ct.currencyconversion var currencyIfc interface{} = ct.currencyconversion
var defaultViewIfc interface{} = ct.defaultView var defaultViewIfc interface{} = ct.defaultView
cmcIfc := map[string]interface{}{
"pro_api_key": ct.apiKeys.cmc,
}
var inputs = &config{ var inputs = &config{
Shortcuts: shortcutsIfcs, Shortcuts: shortcutsIfcs,
Favorites: favoritesIfcs, Favorites: favoritesIfcs,
Portfolio: portfolioIfc, Portfolio: portfolioIfc,
Currency: currencyIfc, Currency: currencyIfc,
DefaultView: defaultViewIfc, DefaultView: defaultViewIfc,
CoinMarketCap: cmcIfc,
} }
var b bytes.Buffer var b bytes.Buffer
@ -223,6 +232,16 @@ func (ct *Cointop) loadDefaultViewFromConfig() error {
return nil return nil
} }
func (ct *Cointop) loadAPIKeysFromConfig() error {
for key, value := range ct.config.CoinMarketCap {
k := strings.TrimSpace(strings.ToLower(key))
if k == "pro_api_key" {
ct.apiKeys.cmc = value.(string)
}
}
return nil
}
func (ct *Cointop) loadFavoritesFromConfig() error { func (ct *Cointop) loadFavoritesFromConfig() error {
for k, arr := range ct.config.Favorites { for k, arr := range ct.config.Favorites {
// DEPRECATED: favorites by 'symbol' is deprecated because of collisions. Kept for backward compatibility. // DEPRECATED: favorites by 'symbol' is deprecated because of collisions. Kept for backward compatibility.

@ -1,6 +1,8 @@
package cointop package cointop
import "log" import (
log "github.com/sirupsen/logrus"
)
func (ct *Cointop) debuglog(s string) { func (ct *Cointop) debuglog(s string) {
if ct.debug { if ct.debug {

@ -14,13 +14,13 @@ func (ct *Cointop) toggleFavorite() error {
ct.favorites[coin.Name] = true ct.favorites[coin.Name] = true
coin.Favorite = true coin.Favorite = true
} }
ct.updateTable() go ct.updateTable()
return nil return nil
} }
func (ct *Cointop) toggleShowFavorites() error { func (ct *Cointop) toggleShowFavorites() error {
ct.portfoliovisible = false ct.portfoliovisible = false
ct.filterByFavorites = !ct.filterByFavorites ct.filterByFavorites = !ct.filterByFavorites
ct.updateTable() go ct.updateTable()
return nil return nil
} }

@ -71,15 +71,13 @@ func (ct *Cointop) layout(g *gocui.Gui) error {
ct.tableview.Highlight = true ct.tableview.Highlight = true
ct.tableview.SelBgColor = gocui.ColorCyan ct.tableview.SelBgColor = gocui.ColorCyan
ct.tableview.SelFgColor = gocui.ColorBlack ct.tableview.SelFgColor = gocui.ColorBlack
_, found := ct.cache.Get("allcoinsslugmap")
if found {
ct.cache.Delete("allcoinsslugmap")
}
go func() { go func() {
ct.updateCoins() ct.updateCoins()
ct.updateTable() ct.updateTable()
_, found := ct.cache.Get("allcoinsslugmap")
if found {
ct.cache.Delete("allcoinsslugmap")
ct.updateCoins()
ct.updateTable()
}
}() }()
} }

@ -2,17 +2,21 @@ package cointop
import ( import (
"sync" "sync"
"time"
types "github.com/miguelmota/cointop/cointop/common/api/types" types "github.com/miguelmota/cointop/cointop/common/api/types"
"github.com/miguelmota/cointop/cointop/common/filecache"
) )
var coinslock sync.Mutex var coinslock sync.Mutex
var updatecoinsmux sync.Mutex
func (ct *Cointop) updateCoins() error { func (ct *Cointop) updateCoins() error {
coinslock.Lock() coinslock.Lock()
defer coinslock.Unlock() defer coinslock.Unlock()
cachekey := "allcoinsslugmap" cachekey := "allcoinsslugmap"
var err error
var allcoinsslugmap map[string]types.Coin var allcoinsslugmap map[string]types.Coin
cached, found := ct.cache.Get(cachekey) cached, found := ct.cache.Get(cachekey)
if found { if found {
@ -24,37 +28,31 @@ func (ct *Cointop) updateCoins() error {
// cache miss // cache miss
if allcoinsslugmap == nil { if allcoinsslugmap == nil {
ct.debuglog("cache miss") ct.debuglog("cache miss")
ch, err := ct.api.GetAllCoinData(ct.currencyconversion) ch := make(chan map[string]types.Coin)
err = ct.api.GetAllCoinData(ct.currencyconversion, ch)
if err != nil { if err != nil {
return err return err
} }
for { for coins := range ch {
coins, ok := <-ch allcoinsslugmap := make(map[string]types.Coin)
if !ok { for _, c := range coins {
break allcoinsslugmap[c.Name] = c
} }
ct.updateCoinsMap(coins, true) go ct.processCoinsMap(allcoinsslugmap)
ct.updateTable() ct.cache.Set(cachekey, ct.allcoinsslugmap, 10*time.Second)
filecache.Set(cachekey, ct.allcoinsslugmap, 24*time.Hour)
} }
/*
ct.cache.Set(cachekey, allcoinsslugmap, 10*time.Second)
go func() {
filecache.Set(cachekey, allcoinsslugmap, 24*time.Hour)
}()
*/
} else { } else {
ct.updateCoinsMap(allcoinsslugmap, false) ct.processCoinsMap(allcoinsslugmap)
} }
return nil return nil
} }
func (ct *Cointop) updateCoinsMap(allcoinsslugmap map[string]types.Coin, b bool) { func (ct *Cointop) processCoinsMap(allcoinsslugmap map[string]types.Coin) {
if len(ct.allcoinsslugmap) == 0 { updatecoinsmux.Lock()
ct.allcoinsslugmap = map[string]*coin{} defer updatecoinsmux.Unlock()
}
for k, v := range allcoinsslugmap { for k, v := range allcoinsslugmap {
last := ct.allcoinsslugmap[k] last := ct.allcoinsslugmap[k]
ct.allcoinsslugmap[k] = &coin{ ct.allcoinsslugmap[k] = &coin{
@ -75,17 +73,15 @@ func (ct *Cointop) updateCoinsMap(allcoinsslugmap map[string]types.Coin, b bool)
if last != nil { if last != nil {
ct.allcoinsslugmap[k].Favorite = last.Favorite ct.allcoinsslugmap[k].Favorite = last.Favorite
} }
if b {
ct.allcoins = append(ct.allcoins, ct.allcoinsslugmap[k])
}
}
//if len(ct.allcoins) == 0 {
if b {
//ct.sort(ct.sortby, ct.sortdesc, ct.allcoins)
} }
if !b { if len(ct.allcoins) < len(ct.allcoinsslugmap) {
list := []*coin{}
for k := range allcoinsslugmap {
coin := ct.allcoinsslugmap[k]
list = append(list, coin)
}
ct.allcoins = append(ct.allcoins, list...)
} else {
// update list in place without changing order // update list in place without changing order
for i := range ct.allcoinsslugmap { for i := range ct.allcoinsslugmap {
cm := ct.allcoinsslugmap[i] cm := ct.allcoinsslugmap[i]
@ -111,4 +107,10 @@ func (ct *Cointop) updateCoinsMap(allcoinsslugmap map[string]types.Coin, b bool)
} }
} }
} }
ct.updateTable()
}
func (ct *Cointop) getListCount() int {
return len(ct.allCoins())
} }

@ -78,7 +78,7 @@ func (ct *Cointop) updateMarketbar() error {
} else { } else {
market, err = ct.api.GetGlobalMarketData(ct.currencyconversion) market, err = ct.api.GetGlobalMarketData(ct.currencyconversion)
if err != nil { if err != nil {
return err filecache.Get(cachekey, &market)
} }
ct.cache.Set(cachekey, market, 10*time.Second) ct.cache.Set(cachekey, market, 10*time.Second)

@ -9,19 +9,19 @@ func (ct *Cointop) currentDisplayPage() int {
} }
func (ct *Cointop) totalPages() int { func (ct *Cointop) totalPages() int {
return (ct.getListCount() / ct.perpage) + 1 return ct.getListCount() / ct.perpage
} }
func (ct *Cointop) totalPerPage() int { func (ct *Cointop) totalPagesDisplay() int {
return ct.perpage return ct.totalPages() + 1
} }
func (ct *Cointop) getListCount() int { func (ct *Cointop) totalPerPage() int {
return len(ct.allCoins()) return ct.perpage
} }
func (ct *Cointop) setPage(page int) int { func (ct *Cointop) setPage(page int) int {
if (page*ct.perpage) <= ct.getListCount() && page >= 0 { if (page*ct.perpage) < ct.getListCount() && page >= 0 {
ct.page = page ct.page = page
} }
return ct.page return ct.page

@ -14,14 +14,14 @@ import (
func (ct *Cointop) togglePortfolio() error { func (ct *Cointop) togglePortfolio() error {
ct.filterByFavorites = false ct.filterByFavorites = false
ct.portfoliovisible = !ct.portfoliovisible ct.portfoliovisible = !ct.portfoliovisible
ct.updateTable() go ct.updateTable()
return nil return nil
} }
func (ct *Cointop) toggleShowPortfolio() error { func (ct *Cointop) toggleShowPortfolio() error {
ct.filterByFavorites = false ct.filterByFavorites = false
ct.portfoliovisible = true ct.portfoliovisible = true
ct.updateTable() go ct.updateTable()
return nil return nil
} }

@ -6,7 +6,10 @@ import (
) )
func (ct *Cointop) refresh() error { func (ct *Cointop) refresh() error {
ct.forcerefresh <- true go func() {
<-ct.limiter
ct.forcerefresh <- true
}()
return nil return nil
} }
@ -16,8 +19,10 @@ func (ct *Cointop) refreshAll() error {
ct.setRefreshStatus() ct.setRefreshStatus()
ct.cache.Delete("allcoinsslugmap") ct.cache.Delete("allcoinsslugmap")
ct.cache.Delete("market") ct.cache.Delete("market")
ct.updateCoins() go func() {
ct.updateTable() ct.updateCoins()
ct.updateTable()
}()
go ct.updateChart() go ct.updateChart()
return nil return nil
} }

@ -2,11 +2,22 @@ package cointop
import ( import (
"sort" "sort"
"sync"
"github.com/jroimartin/gocui" "github.com/jroimartin/gocui"
) )
func (ct *Cointop) sort(sortby string, desc bool, list []*coin) { var sortlock sync.Mutex
func (ct *Cointop) sort(sortby string, desc bool, list []*coin, renderHeaders bool) {
sortlock.Lock()
defer sortlock.Unlock()
if list == nil {
return
}
if len(list) < 2 {
return
}
ct.sortby = sortby ct.sortby = sortby
ct.sortdesc = desc ct.sortdesc = desc
sort.Slice(list[:], func(i, j int) bool { sort.Slice(list[:], func(i, j int) bool {
@ -54,7 +65,10 @@ func (ct *Cointop) sort(sortby string, desc bool, list []*coin) {
return a.Rank < b.Rank return a.Rank < b.Rank
} }
}) })
ct.updateHeaders()
if renderHeaders {
ct.updateHeaders()
}
} }
func (ct *Cointop) sortToggle(sortby string, desc bool) error { func (ct *Cointop) sortToggle(sortby string, desc bool) error {
@ -62,7 +76,7 @@ func (ct *Cointop) sortToggle(sortby string, desc bool) error {
desc = !ct.sortdesc desc = !ct.sortdesc
} }
ct.sort(sortby, desc, ct.coins) ct.sort(sortby, desc, ct.coins, true)
ct.updateTable() ct.updateTable()
return nil return nil
} }
@ -84,14 +98,12 @@ func (ct *Cointop) getSortColIndex() int {
func (ct *Cointop) sortAsc() error { func (ct *Cointop) sortAsc() error {
ct.sortdesc = false ct.sortdesc = false
ct.sort(ct.sortby, ct.sortdesc, ct.coins)
ct.updateTable() ct.updateTable()
return nil return nil
} }
func (ct *Cointop) sortDesc() error { func (ct *Cointop) sortDesc() error {
ct.sortdesc = true ct.sortdesc = true
ct.sort(ct.sortby, ct.sortdesc, ct.coins)
ct.updateTable() ct.updateTable()
return nil return nil
} }
@ -104,7 +116,7 @@ func (ct *Cointop) sortPrevCol() error {
k = 0 k = 0
} }
nextsortby = ct.tablecolumnorder[k] nextsortby = ct.tablecolumnorder[k]
ct.sort(nextsortby, ct.sortdesc, ct.coins) ct.sort(nextsortby, ct.sortdesc, ct.coins, true)
ct.updateTable() ct.updateTable()
return nil return nil
} }
@ -118,7 +130,7 @@ func (ct *Cointop) sortNextCol() error {
k = l - 1 k = l - 1
} }
nextsortby = ct.tablecolumnorder[k] nextsortby = ct.tablecolumnorder[k]
ct.sort(nextsortby, ct.sortdesc, ct.coins) ct.sort(nextsortby, ct.sortdesc, ct.coins, true)
ct.updateTable() ct.updateTable()
return nil return nil
} }

@ -8,7 +8,7 @@ import (
func (ct *Cointop) updateStatusbar(s string) { func (ct *Cointop) updateStatusbar(s string) {
currpage := ct.currentDisplayPage() currpage := ct.currentDisplayPage()
totalpages := ct.totalPages() totalpages := ct.totalPagesDisplay()
var quitText string var quitText string
var favoritesText string var favoritesText string
var portfolioText string var portfolioText string

@ -136,7 +136,7 @@ func (ct *Cointop) refreshTable() error {
namecolor(pad.Right(fmt.Sprintf("%.22s", name), 21, " ")), namecolor(pad.Right(fmt.Sprintf("%.22s", name), 21, " ")),
color.White(pad.Right(fmt.Sprintf("%.6s", coin.Symbol), symbolpadding, " ")), color.White(pad.Right(fmt.Sprintf("%.6s", coin.Symbol), symbolpadding, " ")),
colorprice(fmt.Sprintf("%12s", humanize.Commaf(coin.Price))), colorprice(fmt.Sprintf("%12s", humanize.Commaf(coin.Price))),
color.White(fmt.Sprintf("%17s", humanize.Commaf(coin.MarketCap))), color.White(fmt.Sprintf("%18s", humanize.Commaf(coin.MarketCap))),
color.White(fmt.Sprintf("%15s", humanize.Commaf(coin.Volume24H))), color.White(fmt.Sprintf("%15s", humanize.Commaf(coin.Volume24H))),
color1h(fmt.Sprintf("%8.2f%%", coin.PercentChange1H)), color1h(fmt.Sprintf("%8.2f%%", coin.PercentChange1H)),
color24h(fmt.Sprintf("%8.2f%%", coin.PercentChange24H)), color24h(fmt.Sprintf("%8.2f%%", coin.PercentChange24H)),
@ -158,7 +158,9 @@ func (ct *Cointop) refreshTable() error {
ct.update(func() { ct.update(func() {
ct.tableview.Clear() ct.tableview.Clear()
ct.table.Format().Fprint(ct.tableview) ct.table.Format().Fprint(ct.tableview)
ct.rowChanged() go ct.rowChanged()
go ct.updateHeaders()
go ct.updateMarketbar()
go ct.updateChart() go ct.updateChart()
}) })
@ -183,16 +185,14 @@ func (ct *Cointop) updateTable() error {
} }
} }
ct.coins = sliced ct.coins = sliced
ct.sort(ct.sortby, ct.sortdesc, ct.coins) go ct.refreshTable()
ct.refreshTable()
return nil return nil
} }
if ct.portfoliovisible { if ct.portfoliovisible {
sliced = ct.getPortfolioSlice() sliced = ct.getPortfolioSlice()
ct.coins = sliced ct.coins = sliced
ct.sort(ct.sortby, ct.sortdesc, ct.coins) go ct.refreshTable()
ct.refreshTable()
return nil return nil
} }
@ -223,8 +223,9 @@ func (ct *Cointop) updateTable() error {
sliced = allcoins[start:end] sliced = allcoins[start:end]
} }
ct.coins = sliced ct.coins = sliced
ct.sort(ct.sortby, ct.sortdesc, ct.coins)
ct.refreshTable() ct.sort(ct.sortby, ct.sortdesc, ct.coins, true)
go ct.refreshTable()
return nil return nil
} }

@ -2,10 +2,15 @@ package cointop
import ( import (
"github.com/jroimartin/gocui" "github.com/jroimartin/gocui"
log "github.com/sirupsen/logrus"
) )
// update update view // update update view
func (ct *Cointop) update(f func()) { func (ct *Cointop) update(f func()) {
if ct.g == nil {
log.Fatal("gocui is not initialized")
}
ct.g.Update(func(g *gocui.Gui) error { ct.g.Update(func(g *gocui.Gui) error {
f() f()
return nil return nil

@ -1,7 +1,7 @@
package cointop package cointop
// TODO: make dynamic based on git tag // TODO: make dynamic based on git tag
const version = "1.1.3" const version = "1.1.4"
func (ct *Cointop) version() string { func (ct *Cointop) version() string {
return version return version

@ -3,6 +3,7 @@ module github.com/miguelmota/cointop
require ( require (
github.com/BurntSushi/toml v0.3.1 github.com/BurntSushi/toml v0.3.1
github.com/anaskhan96/soup v1.1.1 // indirect github.com/anaskhan96/soup v1.1.1 // indirect
github.com/davecgh/go-spew v1.1.1
github.com/fatih/color v1.7.0 github.com/fatih/color v1.7.0
github.com/gizak/termui v2.3.0+incompatible github.com/gizak/termui v2.3.0+incompatible
github.com/jroimartin/gocui v0.4.0 github.com/jroimartin/gocui v0.4.0
@ -15,6 +16,7 @@ require (
github.com/mitchellh/go-wordwrap v1.0.0 // indirect github.com/mitchellh/go-wordwrap v1.0.0 // indirect
github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d // indirect github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/sirupsen/logrus v1.4.1
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a // indirect golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a // indirect
golang.org/x/net v0.0.0-20190415214537-1da14a5a36f2 // indirect golang.org/x/net v0.0.0-20190415214537-1da14a5a36f2 // indirect
golang.org/x/sys v0.0.0-20190416152802-12500544f89f // indirect golang.org/x/sys v0.0.0-20190416152802-12500544f89f // indirect

@ -11,6 +11,7 @@ github.com/gizak/termui v2.3.0+incompatible h1:S8wJoNumYfc/rR5UezUM4HsPEo3RJh0LK
github.com/gizak/termui v2.3.0+incompatible/go.mod h1:PkJoWUt/zacQKysNfQtcw1RW+eK2SxkieVBtl+4ovLA= github.com/gizak/termui v2.3.0+incompatible/go.mod h1:PkJoWUt/zacQKysNfQtcw1RW+eK2SxkieVBtl+4ovLA=
github.com/jroimartin/gocui v0.4.0 h1:52jnalstgmc25FmtGcWqa0tcbMEWS6RpFLsOIO+I+E8= github.com/jroimartin/gocui v0.4.0 h1:52jnalstgmc25FmtGcWqa0tcbMEWS6RpFLsOIO+I+E8=
github.com/jroimartin/gocui v0.4.0/go.mod h1:7i7bbj99OgFHzo7kB2zPb8pXLqMBSQegY7azfqXMkyY= github.com/jroimartin/gocui v0.4.0/go.mod h1:7i7bbj99OgFHzo7kB2zPb8pXLqMBSQegY7azfqXMkyY=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/maruel/panicparse v1.1.1 h1:k62YPcEoLncEEpjMt92GtG5ugb8WL/510Ys3/h5IkRc= github.com/maruel/panicparse v1.1.1 h1:k62YPcEoLncEEpjMt92GtG5ugb8WL/510Ys3/h5IkRc=
github.com/maruel/panicparse v1.1.1/go.mod h1:nty42YY5QByNC5MM7q/nj938VbgPU7avs45z6NClpxI= github.com/maruel/panicparse v1.1.1/go.mod h1:nty42YY5QByNC5MM7q/nj938VbgPU7avs45z6NClpxI=
github.com/maruel/panicparse v1.1.2-0.20180806203336-f20d4c4d746f h1:mtX2D0ta3lWxCvv276VVIH6mMYzm4jhSfYP70ZJSOQU= github.com/maruel/panicparse v1.1.2-0.20180806203336-f20d4c4d746f h1:mtX2D0ta3lWxCvv276VVIH6mMYzm4jhSfYP70ZJSOQU=
@ -36,6 +37,11 @@ github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d h1:x3S6kxmy49zXVVyh
github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ= github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/net v0.0.0-20180215212450-dc948dff8834/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180215212450-dc948dff8834/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -43,6 +49,7 @@ golang.org/x/net v0.0.0-20181220203305-927f97764cc3 h1:eH6Eip3UpmR+yM/qI9Ijluzb1
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190415214537-1da14a5a36f2 h1:iC0Y6EDq+rhnAePxGvJs2kzUAYcwESqdcGRPzEUfzTU= golang.org/x/net v0.0.0-20190415214537-1da14a5a36f2 h1:iC0Y6EDq+rhnAePxGvJs2kzUAYcwESqdcGRPzEUfzTU=
golang.org/x/net v0.0.0-20190415214537-1da14a5a36f2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190415214537-1da14a5a36f2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8=

Loading…
Cancel
Save