diff --git a/.gitignore b/.gitignore index 4d32b7d..2164e66 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,8 @@ bfg.jar build dist bin +main +!main.go # flatpak .flatpak-builder diff --git a/CHANGELOG.md b/CHANGELOG.md index e00974a..7517af5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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/), 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 ### Fixed - Paginate CoinMarketCap V1 API responses due to their backward-incompatible update diff --git a/Makefile b/Makefile index ad3c6fb..d19aecf 100644 --- a/Makefile +++ b/Makefile @@ -10,6 +10,9 @@ deps: debug: DEBUG=1 go run main.go +build: + @go build main.go + # http://macappstore.org/upx build/mac: clean/mac env GOARCH=amd64 go build -ldflags "-s -w" -o bin/macos/cointop && upx bin/macos/cointop diff --git a/README.md b/README.md index fefd3b2..cbb11c8 100644 --- a/README.md +++ b/README.md @@ -354,7 +354,7 @@ You can then configure the actions you want for each key: ```toml currency = "USD" -defaultView = "default" +defaultView = "" [shortcuts] "$" = "last_page" @@ -422,6 +422,13 @@ defaultView = "default" t = "sort_column_total_supply" u = "sort_column_last_updated" 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: @@ -519,7 +526,14 @@ Frequently asked questions: - 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. diff --git a/cointop/cointop.go b/cointop/cointop.go index 20c3149..ab54666 100644 --- a/cointop/cointop.go +++ b/cointop/cointop.go @@ -3,7 +3,6 @@ package cointop import ( "fmt" "io/ioutil" - "log" "os" "strings" "sync" @@ -16,6 +15,7 @@ import ( "github.com/miguelmota/cointop/cointop/common/filecache" "github.com/miguelmota/cointop/cointop/common/table" "github.com/patrickmn/go-cache" + log "github.com/sirupsen/logrus" ) // TODO: clean up and optimize codebase @@ -82,6 +82,8 @@ type Cointop struct { inputview *gocui.View inputviewname string defaultView string + apiKeys *apiKeys + limiter <-chan time.Time } // PortfolioEntry is portfolio entry @@ -100,6 +102,11 @@ type Config struct { ConfigFilepath string } +// apiKeys is api keys structure +type apiKeys struct { + cmc string +} + var defaultConfigPath = "~/.cointop/config" // NewCointop initializes cointop @@ -117,18 +124,19 @@ func NewCointop(config *Config) *Cointop { } ct := &Cointop{ - api: api.NewCMC(), - refreshticker: time.NewTicker(1 * time.Minute), - sortby: "rank", - page: 0, - perpage: 100, - forcerefresh: make(chan bool), - maxtablewidth: 175, - actionsmap: actionsMap(), - shortcutkeys: defaultShortcuts(), + allcoinsslugmap: make(map[string]*coin), + allcoins: []*coin{}, + refreshticker: time.NewTicker(1 * time.Minute), + sortby: "rank", + page: 0, + perpage: 100, + forcerefresh: make(chan bool), + maxtablewidth: 175, + actionsmap: actionsMap(), + shortcutkeys: defaultShortcuts(), // DEPRECATED: favorites by 'symbol' is deprecated because of collisions. Kept for backward compatibility. - favoritesbysymbol: map[string]bool{}, - favorites: map[string]bool{}, + favoritesbysymbol: make(map[string]bool), + favorites: make(map[string]bool), cache: cache.New(1*time.Minute, 2*time.Minute), debug: debug, configFilepath: configFilepath, @@ -189,21 +197,36 @@ func NewCointop(config *Config) *Cointop { }, portfolioupdatemenuviewname: "portfolioupdatemenu", inputviewname: "input", + apiKeys: new(apiKeys), + limiter: time.Tick(2 * time.Second), } + err := ct.setupConfig() if err != nil { log.Fatal(err) } - allcoinsslugmap := map[string]types.Coin{} + ct.api = api.NewCMC(ct.apiKeys.cmc) + coinscachekey := "allcoinsslugmap" - filecache.Get(coinscachekey, &allcoinsslugmap) - ct.cache.Set(coinscachekey, allcoinsslugmap, 10*time.Second) + filecache.Get(coinscachekey, &ct.allcoinsslugmap) + + 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. // 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 { - coin := allcoinsslugmap[i] + for i := range ct.allcoinsslugmap { + coin := ct.allcoinsslugmap[i] for k := range ct.favoritesbysymbol { if coin.Symbol == k { ct.favorites[coin.Name] = true diff --git a/cointop/common/api/api.go b/cointop/common/api/api.go index dd70273..87e3b54 100644 --- a/cointop/common/api/api.go +++ b/cointop/common/api/api.go @@ -5,8 +5,8 @@ import ( ) // NewCMC new CoinMarketCap API -func NewCMC() Interface { - return cmc.New() +func NewCMC(apiKey string) Interface { + return cmc.New(apiKey) } // NewCC new CryptoCompare API diff --git a/cointop/common/api/impl/coinmarketcap/coinmarketcap.go b/cointop/common/api/impl/coinmarketcap/coinmarketcap.go index cbb7f40..7dca567 100644 --- a/cointop/common/api/impl/coinmarketcap/coinmarketcap.go +++ b/cointop/common/api/impl/coinmarketcap/coinmarketcap.go @@ -1,12 +1,10 @@ package coinmarketcap import ( - "errors" "fmt" "os" "strconv" "strings" - "sync" "time" apitypes "github.com/miguelmota/cointop/cointop/common/api/types" @@ -20,9 +18,12 @@ type Service struct { } // 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{ - ProAPIKey: os.Getenv("CMC_PRO_API_KEY"), + ProAPIKey: apiKey, }) return &Service{ client: client, @@ -31,15 +32,18 @@ func New() *Service { // Ping ping API func (s *Service) Ping() error { - info, err := s.client.Cryptocurrency.Info(&cmc.InfoOptions{ - Symbol: "BTC", - }) - if err != nil { - return errors.New("failed to ping") - } - if info == nil { - return errors.New("failed to ping") - } + // TODO: notify in statusbar of failed ping (instead of fatal to make it work offline) + /* + info, err := s.client.Cryptocurrency.Info(&cmc.InfoOptions{ + Symbol: "BTC", + }) + if err != nil { + return errors.New("failed to ping") + } + if info == nil { + return errors.New("failed to ping") + } + */ 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 -func (s *Service) GetAllCoinData(convert string) (chan map[string]apitypes.Coin, error) { - var wg sync.WaitGroup - ch := make(chan map[string]apitypes.Coin) +func (s *Service) GetAllCoinData(convert string, ch chan map[string]apitypes.Coin) error { go func() { - var mutex sync.Mutex - maxPages := 15 + maxPages := 10 + defer close(ch) for i := 0; i < maxPages; i++ { - time.Sleep(time.Duration(i) * time.Second) - wg.Add(1) - go func(j int) { - defer wg.Done() - coins, err := s.getLimitedCoinData(convert, j) - if err != nil { - return - } - mutex.Lock() - defer mutex.Unlock() - ret := make(map[string]apitypes.Coin) - for k, v := range coins { - ret[k] = v - } - ch <- ret - }(i) + if i > 0 { + time.Sleep(1 * time.Second) + } + coins, err := s.getLimitedCoinData(convert, i) + if err != nil { + return + } + ret := make(map[string]apitypes.Coin) + for k, v := range coins { + ret[k] = v + } + ch <- ret } - wg.Wait() }() - return ch, nil + return nil } // GetCoinGraphData gets coin graph data diff --git a/cointop/common/api/interface.go b/cointop/common/api/interface.go index 2a4a6c7..f526461 100644 --- a/cointop/common/api/interface.go +++ b/cointop/common/api/interface.go @@ -7,7 +7,7 @@ import ( // Interface interface type Interface interface { 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) GetGlobalMarketGraphData(start int64, end int64) (types.MarketGraph, error) GetGlobalMarketData(convert string) (types.GlobalMarketData, error) diff --git a/cointop/config.go b/cointop/config.go index 2675761..e0047b5 100644 --- a/cointop/config.go +++ b/cointop/config.go @@ -12,11 +12,12 @@ import ( var fileperm = os.FileMode(0644) type config struct { - Shortcuts map[string]interface{} `toml:"shortcuts"` - Favorites map[string][]interface{} `toml:"favorites"` - Portfolio map[string]interface{} `toml:"portfolio"` - Currency interface{} `toml:"currency"` - DefaultView interface{} `toml:"defaultView"` + Shortcuts map[string]interface{} `toml:"shortcuts"` + Favorites map[string][]interface{} `toml:"favorites"` + Portfolio map[string]interface{} `toml:"portfolio"` + Currency interface{} `toml:"currency"` + DefaultView interface{} `toml:"defaultView"` + CoinMarketCap map[string]interface{} `toml:"coinmarketcap"` } func (ct *Cointop) setupConfig() error { @@ -48,6 +49,10 @@ func (ct *Cointop) setupConfig() error { if err != nil { return err } + err = ct.loadAPIKeysFromConfig() + if err != nil { + return err + } return nil } @@ -162,13 +167,17 @@ func (ct *Cointop) configToToml() ([]byte, error) { var currencyIfc interface{} = ct.currencyconversion var defaultViewIfc interface{} = ct.defaultView + cmcIfc := map[string]interface{}{ + "pro_api_key": ct.apiKeys.cmc, + } var inputs = &config{ - Shortcuts: shortcutsIfcs, - Favorites: favoritesIfcs, - Portfolio: portfolioIfc, - Currency: currencyIfc, - DefaultView: defaultViewIfc, + Shortcuts: shortcutsIfcs, + Favorites: favoritesIfcs, + Portfolio: portfolioIfc, + Currency: currencyIfc, + DefaultView: defaultViewIfc, + CoinMarketCap: cmcIfc, } var b bytes.Buffer @@ -223,6 +232,16 @@ func (ct *Cointop) loadDefaultViewFromConfig() error { 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 { for k, arr := range ct.config.Favorites { // DEPRECATED: favorites by 'symbol' is deprecated because of collisions. Kept for backward compatibility. diff --git a/cointop/debug.go b/cointop/debug.go index 87be06a..7bcf101 100644 --- a/cointop/debug.go +++ b/cointop/debug.go @@ -1,6 +1,8 @@ package cointop -import "log" +import ( + log "github.com/sirupsen/logrus" +) func (ct *Cointop) debuglog(s string) { if ct.debug { diff --git a/cointop/favorites.go b/cointop/favorites.go index 222547a..aea336f 100644 --- a/cointop/favorites.go +++ b/cointop/favorites.go @@ -14,13 +14,13 @@ func (ct *Cointop) toggleFavorite() error { ct.favorites[coin.Name] = true coin.Favorite = true } - ct.updateTable() + go ct.updateTable() return nil } func (ct *Cointop) toggleShowFavorites() error { ct.portfoliovisible = false ct.filterByFavorites = !ct.filterByFavorites - ct.updateTable() + go ct.updateTable() return nil } diff --git a/cointop/layout.go b/cointop/layout.go index 22df7de..b70dd7a 100644 --- a/cointop/layout.go +++ b/cointop/layout.go @@ -71,15 +71,13 @@ func (ct *Cointop) layout(g *gocui.Gui) error { ct.tableview.Highlight = true ct.tableview.SelBgColor = gocui.ColorCyan ct.tableview.SelFgColor = gocui.ColorBlack + _, found := ct.cache.Get("allcoinsslugmap") + if found { + ct.cache.Delete("allcoinsslugmap") + } go func() { ct.updateCoins() ct.updateTable() - _, found := ct.cache.Get("allcoinsslugmap") - if found { - ct.cache.Delete("allcoinsslugmap") - ct.updateCoins() - ct.updateTable() - } }() } diff --git a/cointop/list.go b/cointop/list.go index 4a99e4b..dec8edf 100644 --- a/cointop/list.go +++ b/cointop/list.go @@ -2,17 +2,21 @@ package cointop import ( "sync" + "time" types "github.com/miguelmota/cointop/cointop/common/api/types" + "github.com/miguelmota/cointop/cointop/common/filecache" ) var coinslock sync.Mutex +var updatecoinsmux sync.Mutex func (ct *Cointop) updateCoins() error { coinslock.Lock() defer coinslock.Unlock() cachekey := "allcoinsslugmap" + var err error var allcoinsslugmap map[string]types.Coin cached, found := ct.cache.Get(cachekey) if found { @@ -24,37 +28,31 @@ func (ct *Cointop) updateCoins() error { // cache miss if allcoinsslugmap == nil { 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 { return err } - for { - coins, ok := <-ch - if !ok { - break + for coins := range ch { + allcoinsslugmap := make(map[string]types.Coin) + for _, c := range coins { + allcoinsslugmap[c.Name] = c } - ct.updateCoinsMap(coins, true) - ct.updateTable() + go ct.processCoinsMap(allcoinsslugmap) + 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 { - ct.updateCoinsMap(allcoinsslugmap, false) + ct.processCoinsMap(allcoinsslugmap) } return nil } -func (ct *Cointop) updateCoinsMap(allcoinsslugmap map[string]types.Coin, b bool) { - if len(ct.allcoinsslugmap) == 0 { - ct.allcoinsslugmap = map[string]*coin{} - } +func (ct *Cointop) processCoinsMap(allcoinsslugmap map[string]types.Coin) { + updatecoinsmux.Lock() + defer updatecoinsmux.Unlock() for k, v := range allcoinsslugmap { last := ct.allcoinsslugmap[k] ct.allcoinsslugmap[k] = &coin{ @@ -75,17 +73,15 @@ func (ct *Cointop) updateCoinsMap(allcoinsslugmap map[string]types.Coin, b bool) if last != nil { 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 for i := range ct.allcoinsslugmap { 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()) } diff --git a/cointop/marketbar.go b/cointop/marketbar.go index dd4168f..e09451c 100644 --- a/cointop/marketbar.go +++ b/cointop/marketbar.go @@ -78,7 +78,7 @@ func (ct *Cointop) updateMarketbar() error { } else { market, err = ct.api.GetGlobalMarketData(ct.currencyconversion) if err != nil { - return err + filecache.Get(cachekey, &market) } ct.cache.Set(cachekey, market, 10*time.Second) diff --git a/cointop/navigation.go b/cointop/navigation.go index 97f4ef7..1c517bf 100644 --- a/cointop/navigation.go +++ b/cointop/navigation.go @@ -9,19 +9,19 @@ func (ct *Cointop) currentDisplayPage() int { } func (ct *Cointop) totalPages() int { - return (ct.getListCount() / ct.perpage) + 1 + return ct.getListCount() / ct.perpage } -func (ct *Cointop) totalPerPage() int { - return ct.perpage +func (ct *Cointop) totalPagesDisplay() int { + return ct.totalPages() + 1 } -func (ct *Cointop) getListCount() int { - return len(ct.allCoins()) +func (ct *Cointop) totalPerPage() int { + return ct.perpage } 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 } return ct.page diff --git a/cointop/portfolio.go b/cointop/portfolio.go index c338008..64bf3f0 100644 --- a/cointop/portfolio.go +++ b/cointop/portfolio.go @@ -14,14 +14,14 @@ import ( func (ct *Cointop) togglePortfolio() error { ct.filterByFavorites = false ct.portfoliovisible = !ct.portfoliovisible - ct.updateTable() + go ct.updateTable() return nil } func (ct *Cointop) toggleShowPortfolio() error { ct.filterByFavorites = false ct.portfoliovisible = true - ct.updateTable() + go ct.updateTable() return nil } diff --git a/cointop/refresh.go b/cointop/refresh.go index a06a196..f7a1616 100644 --- a/cointop/refresh.go +++ b/cointop/refresh.go @@ -6,7 +6,10 @@ import ( ) func (ct *Cointop) refresh() error { - ct.forcerefresh <- true + go func() { + <-ct.limiter + ct.forcerefresh <- true + }() return nil } @@ -16,8 +19,10 @@ func (ct *Cointop) refreshAll() error { ct.setRefreshStatus() ct.cache.Delete("allcoinsslugmap") ct.cache.Delete("market") - ct.updateCoins() - ct.updateTable() + go func() { + ct.updateCoins() + ct.updateTable() + }() go ct.updateChart() return nil } diff --git a/cointop/sort.go b/cointop/sort.go index ac0e9bb..d9adeb4 100644 --- a/cointop/sort.go +++ b/cointop/sort.go @@ -2,11 +2,22 @@ package cointop import ( "sort" + "sync" "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.sortdesc = desc 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 } }) - ct.updateHeaders() + + if renderHeaders { + ct.updateHeaders() + } } 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 } - ct.sort(sortby, desc, ct.coins) + ct.sort(sortby, desc, ct.coins, true) ct.updateTable() return nil } @@ -84,14 +98,12 @@ func (ct *Cointop) getSortColIndex() int { func (ct *Cointop) sortAsc() error { ct.sortdesc = false - ct.sort(ct.sortby, ct.sortdesc, ct.coins) ct.updateTable() return nil } func (ct *Cointop) sortDesc() error { ct.sortdesc = true - ct.sort(ct.sortby, ct.sortdesc, ct.coins) ct.updateTable() return nil } @@ -104,7 +116,7 @@ func (ct *Cointop) sortPrevCol() error { k = 0 } nextsortby = ct.tablecolumnorder[k] - ct.sort(nextsortby, ct.sortdesc, ct.coins) + ct.sort(nextsortby, ct.sortdesc, ct.coins, true) ct.updateTable() return nil } @@ -118,7 +130,7 @@ func (ct *Cointop) sortNextCol() error { k = l - 1 } nextsortby = ct.tablecolumnorder[k] - ct.sort(nextsortby, ct.sortdesc, ct.coins) + ct.sort(nextsortby, ct.sortdesc, ct.coins, true) ct.updateTable() return nil } diff --git a/cointop/statusbar.go b/cointop/statusbar.go index 2898a67..96ed45a 100644 --- a/cointop/statusbar.go +++ b/cointop/statusbar.go @@ -8,7 +8,7 @@ import ( func (ct *Cointop) updateStatusbar(s string) { currpage := ct.currentDisplayPage() - totalpages := ct.totalPages() + totalpages := ct.totalPagesDisplay() var quitText string var favoritesText string var portfolioText string diff --git a/cointop/table.go b/cointop/table.go index faa1de0..564e426 100644 --- a/cointop/table.go +++ b/cointop/table.go @@ -136,7 +136,7 @@ func (ct *Cointop) refreshTable() error { namecolor(pad.Right(fmt.Sprintf("%.22s", name), 21, " ")), color.White(pad.Right(fmt.Sprintf("%.6s", coin.Symbol), symbolpadding, " ")), 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))), color1h(fmt.Sprintf("%8.2f%%", coin.PercentChange1H)), color24h(fmt.Sprintf("%8.2f%%", coin.PercentChange24H)), @@ -158,7 +158,9 @@ func (ct *Cointop) refreshTable() error { ct.update(func() { ct.tableview.Clear() ct.table.Format().Fprint(ct.tableview) - ct.rowChanged() + go ct.rowChanged() + go ct.updateHeaders() + go ct.updateMarketbar() go ct.updateChart() }) @@ -183,16 +185,14 @@ func (ct *Cointop) updateTable() error { } } ct.coins = sliced - ct.sort(ct.sortby, ct.sortdesc, ct.coins) - ct.refreshTable() + go ct.refreshTable() return nil } if ct.portfoliovisible { sliced = ct.getPortfolioSlice() ct.coins = sliced - ct.sort(ct.sortby, ct.sortdesc, ct.coins) - ct.refreshTable() + go ct.refreshTable() return nil } @@ -223,8 +223,9 @@ func (ct *Cointop) updateTable() error { sliced = allcoins[start:end] } 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 } diff --git a/cointop/update.go b/cointop/update.go index b9051a1..3a33907 100644 --- a/cointop/update.go +++ b/cointop/update.go @@ -2,10 +2,15 @@ package cointop import ( "github.com/jroimartin/gocui" + log "github.com/sirupsen/logrus" ) // update update view func (ct *Cointop) update(f func()) { + if ct.g == nil { + log.Fatal("gocui is not initialized") + } + ct.g.Update(func(g *gocui.Gui) error { f() return nil diff --git a/cointop/version.go b/cointop/version.go index 50b2fa7..f12ba4f 100644 --- a/cointop/version.go +++ b/cointop/version.go @@ -1,7 +1,7 @@ package cointop // TODO: make dynamic based on git tag -const version = "1.1.3" +const version = "1.1.4" func (ct *Cointop) version() string { return version diff --git a/go.mod b/go.mod index f29cdd8..9280680 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/miguelmota/cointop require ( github.com/BurntSushi/toml v0.3.1 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/gizak/termui v2.3.0+incompatible github.com/jroimartin/gocui v0.4.0 @@ -15,6 +16,7 @@ require ( github.com/mitchellh/go-wordwrap v1.0.0 // indirect github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d // indirect 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/net v0.0.0-20190415214537-1da14a5a36f2 // indirect golang.org/x/sys v0.0.0-20190416152802-12500544f89f // indirect diff --git a/go.sum b/go.sum index 39fc880..9e7e4a4 100644 --- a/go.sum +++ b/go.sum @@ -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/jroimartin/gocui v0.4.0 h1:52jnalstgmc25FmtGcWqa0tcbMEWS6RpFLsOIO+I+E8= 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/go.mod h1:nty42YY5QByNC5MM7q/nj938VbgPU7avs45z6NClpxI= 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/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/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-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= 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-20190415214537-1da14a5a36f2 h1:iC0Y6EDq+rhnAePxGvJs2kzUAYcwESqdcGRPzEUfzTU= 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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8=