diff --git a/cointop/config.go b/cointop/config.go index 40b7af3..c921ef6 100644 --- a/cointop/config.go +++ b/cointop/config.go @@ -47,11 +47,14 @@ type ConfigFileConfig struct { API interface{} `toml:"api"` Colorscheme interface{} `toml:"colorscheme"` RefreshRate interface{} `toml:"refresh_rate"` + CoinStructHash interface{} `toml:"coin_struct_version"` CacheDir interface{} `toml:"cache_dir"` - CompactNotation interface{} `toml:"compact_notation"` - EnableMouse interface{} `toml:"enable_mouse"` - Table map[string]interface{} `toml:"table"` - Chart map[string]interface{} `toml:"chart"` + + CompactNotation interface{} `toml:"compact_notation"` + EnableMouse interface{} `toml:"enable_mouse"` + + Table map[string]interface{} `toml:"table"` + Chart map[string]interface{} `toml:"chart"` } // SetupConfig loads config file @@ -278,7 +281,7 @@ func (ct *Cointop) ConfigToToml() ([]byte, error) { "height": ct.State.chartHeight, } - var inputs = &ConfigFileConfig{ + inputs := &ConfigFileConfig{ API: ct.apiChoice, Colorscheme: ct.colorschemeName, CoinMarketCap: cmcIfc, @@ -293,6 +296,7 @@ func (ct *Cointop) ConfigToToml() ([]byte, error) { CacheDir: ct.State.cacheDir, Table: tableMapIfc, Chart: chartMapIfc, + CoinStructHash: getStructHash(Coin{}), CompactNotation: ct.State.compactNotation, EnableMouse: ct.State.enableMouse, } diff --git a/cointop/list.go b/cointop/list.go index 12081d5..ac275c4 100644 --- a/cointop/list.go +++ b/cointop/list.go @@ -29,9 +29,11 @@ func (ct *Cointop) UpdateCoins() error { log.Debug("UpdateCoins() soft cache hit") } - // cache miss - if allCoinsSlugMap == nil { - log.Debug("UpdateCoins() cache miss") + // cache miss or coin struct has been changed from the last time + isCacheMissed := allCoinsSlugMap == nil + isCoinStructHashChanged := getStructHash(Coin{}) != ct.config.CoinStructHash + if isCacheMissed || isCoinStructHashChanged { + log.Debug("UpdateCoins() cache miss or coin struct has changed") ch := make(chan []types.Coin) err = ct.api.GetAllCoinData(ct.State.currencyConversion, ch) if err != nil { diff --git a/cointop/util.go b/cointop/util.go index 6d2b96b..a9cdc7b 100644 --- a/cointop/util.go +++ b/cointop/util.go @@ -2,8 +2,10 @@ package cointop import ( "bytes" + "crypto/sha256" "encoding/gob" "fmt" + "reflect" "regexp" "strings" "sync" @@ -66,3 +68,34 @@ func normalizeFloatString(input string, allowNegative bool) string { return "" } + +func getStructHash(x interface{}) string { + raw := make(map[string]string) + collectTypeFields(reflect.TypeOf(x), raw) + + h := sha256.New() + h.Write([]byte(fmt.Sprintf("%v", raw))) + + return fmt.Sprintf("%x", h.Sum(nil)) +} + +func collectTypeFields(t reflect.Type, m map[string]string) { + // Return if not struct or pointer to struct. + if t.Kind() == reflect.Ptr { + t = t.Elem() + } + if t.Kind() != reflect.Struct { + return + } + + // Iterate through fields collecting names in map. + for i := 0; i < t.NumField(); i++ { + sf := t.Field(i) + m[sf.Name] = fmt.Sprintf("%v", sf) + + // Recurse into anonymous fields. + if t.Kind() == reflect.Struct { + collectTypeFields(sf.Type, m) + } + } +} diff --git a/cointop/util_test.go b/cointop/util_test.go new file mode 100644 index 0000000..449940f --- /dev/null +++ b/cointop/util_test.go @@ -0,0 +1,86 @@ +package cointop + +import "testing" + +func Test_getStructHash(t *testing.T) { + type SCoin struct { + Name string + Properties struct { + P7D int + P10D int + } + } + type SCoin1 struct { + Name string + Properties struct { + P7D int + P10D int + } + } + type SCoin2 struct { + Name interface{} + Properties struct { + P7D int + P10D int + } + } + type SCoin3 struct { + Name string + Age int + Properties *struct { + P7D int + P10D int + } + } + type args struct { + str1 interface{} + str2 interface{} + } + + tests := []struct { + name string + args args + want bool + }{ + { + name: "the same structs", + args: args{ + str1: SCoin{}, + str2: SCoin{}, + }, + want: true, + }, + { + name: "different structs but have similar fields", + args: args{ + str1: SCoin{}, + str2: SCoin1{}, + }, + want: true, + }, + { + name: "different structs but have similar fields and different field type", + args: args{ + str1: SCoin{}, + str2: SCoin2{}, + }, + want: false, + }, + { + name: "different structs and different fields", + args: args{ + str1: SCoin{}, + str2: SCoin3{}, + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cp := getStructHash(tt.args.str1) == getStructHash(tt.args.str2) + if cp != tt.want { + t.Errorf("getStructHash() = %v, want %v", cp, tt.want) + } + }) + } +}