Files
gocryptotrader/config/config.go
2017-07-31 11:44:28 +10:00

407 lines
12 KiB
Go

package config
import (
"encoding/json"
"errors"
"fmt"
"log"
"os"
"strconv"
"strings"
"time"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/currency"
"github.com/thrasher-/gocryptotrader/portfolio"
)
// Constants declared here are filename strings and test strings
const (
ConfigFile = "config.dat"
OldConfigFile = "config.json"
ConfigTestFile = "../testdata/configtest.dat"
configFileEncryptionPrompt = 0
configFileEncryptionEnabled = 1
configFileEncryptionDisabled = -1
)
// Variables here are mainly alerts and a configuration object
var (
ErrExchangeNameEmpty = "Exchange #%d in config: Exchange name is empty."
ErrExchangeAvailablePairsEmpty = "Exchange %s: Available pairs is empty."
ErrExchangeEnabledPairsEmpty = "Exchange %s: Enabled pairs is empty."
ErrExchangeBaseCurrenciesEmpty = "Exchange %s: Base currencies is empty."
ErrExchangeNotFound = "Exchange %s: Not found."
ErrNoEnabledExchanges = "No Exchanges enabled."
ErrCryptocurrenciesEmpty = "Cryptocurrencies variable is empty."
ErrFailureOpeningConfig = "Fatal error opening %s file. Error: %s"
ErrCheckingConfigValues = "Fatal error checking config values. Error: %s"
ErrSavingConfigBytesMismatch = "Config file %q bytes comparison doesn't match, read %s expected %s."
WarningSMSGlobalDefaultOrEmptyValues = "WARNING -- SMS Support disabled due to default or empty Username/Password values."
WarningSSMSGlobalSMSContactDefaultOrEmptyValues = "WARNING -- SMS contact #%d Name/Number disabled due to default or empty values."
WarningSSMSGlobalSMSNoContacts = "WARNING -- SMS Support disabled due to no enabled contacts."
WarningWebserverCredentialValuesEmpty = "WARNING -- Webserver support disabled due to empty Username/Password values."
WarningWebserverListenAddressInvalid = "WARNING -- Webserver support disabled due to invalid listen address."
WarningWebserverRootWebFolderNotFound = "WARNING -- Webserver support disabled due to missing web folder."
WarningExchangeAuthAPIDefaultOrEmptyValues = "WARNING -- Exchange %s: Authenticated API support disabled due to default/empty APIKey/Secret/ClientID values."
RenamingConfigFile = "Renaming config file %s to %s."
Cfg Config
)
// WebserverConfig struct holds the prestart variables for the webserver.
type WebserverConfig struct {
Enabled bool
AdminUsername string
AdminPassword string
ListenAddress string
}
// SMSGlobalConfig structure holds all the variables you need for instant
// messaging and broadcast used by SMSGlobal
type SMSGlobalConfig struct {
Enabled bool
Username string
Password string
Contacts []struct {
Name string
Number string
Enabled bool
}
}
// Post holds the bot configuration data
type Post struct {
Data Config `json:"Data"`
}
// Config is the overarching object that holds all the information for
// prestart management of portfolio, SMSGlobal, webserver and enabled exchange
type Config struct {
Name string
EncryptConfig int
Cryptocurrencies string
Portfolio portfolio.Base `json:"PortfolioAddresses"`
SMS SMSGlobalConfig `json:"SMSGlobal"`
Webserver WebserverConfig `json:"Webserver"`
Exchanges []ExchangeConfig `json:"Exchanges"`
}
// ExchangeConfig holds all the information needed for each enabled Exchange.
type ExchangeConfig struct {
Name string
Enabled bool
Verbose bool
Websocket bool
RESTPollingDelay time.Duration
AuthenticatedAPISupport bool
APIKey string
APISecret string
ClientID string `json:",omitempty"`
AvailablePairs string
EnabledPairs string
BaseCurrencies string
}
// GetConfigEnabledExchanges returns the number of exchanges that are enabled.
func (c *Config) GetConfigEnabledExchanges() int {
counter := 0
for i := range c.Exchanges {
if c.Exchanges[i].Enabled {
counter++
}
}
return counter
}
// GetExchangeConfig returns your exchange configurations by its indivdual name
func (c *Config) GetExchangeConfig(name string) (ExchangeConfig, error) {
for i := range c.Exchanges {
if c.Exchanges[i].Name == name {
return c.Exchanges[i], nil
}
}
return ExchangeConfig{}, fmt.Errorf(ErrExchangeNotFound, name)
}
// UpdateExchangeConfig updates exchange configurations
func (c *Config) UpdateExchangeConfig(e ExchangeConfig) error {
for i := range c.Exchanges {
if c.Exchanges[i].Name == e.Name {
c.Exchanges[i] = e
return nil
}
}
return fmt.Errorf(ErrExchangeNotFound, e.Name)
}
// CheckSMSGlobalConfigValues checks concurrent SMSGlobal configurations
func (c *Config) CheckSMSGlobalConfigValues() error {
if c.SMS.Username == "" || c.SMS.Username == "Username" || c.SMS.Password == "" || c.SMS.Password == "Password" {
return errors.New(WarningSMSGlobalDefaultOrEmptyValues)
}
contacts := 0
for i := range c.SMS.Contacts {
if c.SMS.Contacts[i].Enabled {
if c.SMS.Contacts[i].Name == "" || c.SMS.Contacts[i].Number == "" || (c.SMS.Contacts[i].Name == "Bob" && c.SMS.Contacts[i].Number == "12345") {
log.Printf(WarningSSMSGlobalSMSContactDefaultOrEmptyValues, i)
continue
}
contacts++
}
}
if contacts == 0 {
return errors.New(WarningSSMSGlobalSMSNoContacts)
}
return nil
}
// CheckExchangeConfigValues returns configuation values for all enabled
// exchanges
func (c *Config) CheckExchangeConfigValues() error {
if c.Cryptocurrencies == "" {
return errors.New(ErrCryptocurrenciesEmpty)
}
exchanges := 0
for i, exch := range c.Exchanges {
if exch.Enabled {
if exch.Name == "" {
return fmt.Errorf(ErrExchangeNameEmpty, i)
}
if exch.AvailablePairs == "" {
return fmt.Errorf(ErrExchangeAvailablePairsEmpty, exch.Name)
}
if exch.EnabledPairs == "" {
return fmt.Errorf(ErrExchangeEnabledPairsEmpty, exch.Name)
}
if exch.BaseCurrencies == "" {
return fmt.Errorf(ErrExchangeBaseCurrenciesEmpty, exch.Name)
}
if exch.AuthenticatedAPISupport { // non-fatal error
if exch.APIKey == "" || exch.APISecret == "" || exch.APIKey == "Key" || exch.APISecret == "Secret" {
c.Exchanges[i].AuthenticatedAPISupport = false
log.Printf(WarningExchangeAuthAPIDefaultOrEmptyValues, exch.Name)
continue
} else if exch.Name == "ITBIT" || exch.Name == "Bitstamp" || exch.Name == "COINUT" || exch.Name == "GDAX" {
if exch.ClientID == "" || exch.ClientID == "ClientID" {
c.Exchanges[i].AuthenticatedAPISupport = false
log.Printf(WarningExchangeAuthAPIDefaultOrEmptyValues, exch.Name)
continue
}
}
}
exchanges++
}
}
if exchanges == 0 {
return errors.New(ErrNoEnabledExchanges)
}
return nil
}
// CheckWebserverConfigValues checks information before webserver starts and
// returns an error if values are incorrect.
func (c *Config) CheckWebserverConfigValues() error {
if c.Webserver.AdminUsername == "" || c.Webserver.AdminPassword == "" {
return errors.New(WarningWebserverCredentialValuesEmpty)
}
if !common.StringContains(c.Webserver.ListenAddress, ":") {
return errors.New(WarningWebserverListenAddressInvalid)
}
portStr := common.SplitStrings(c.Webserver.ListenAddress, ":")[1]
port, err := strconv.Atoi(portStr)
if err != nil {
return errors.New(WarningWebserverListenAddressInvalid)
}
if port < 1 || port > 65355 {
return errors.New(WarningWebserverListenAddressInvalid)
}
return nil
}
// RetrieveConfigCurrencyPairs splits, assigns and verifies enabled currency
// pairs either cryptoCurrencies or fiatCurrencies
func (c *Config) RetrieveConfigCurrencyPairs() error {
cryptoCurrencies := common.SplitStrings(c.Cryptocurrencies, ",")
fiatCurrencies := common.SplitStrings(currency.DefaultCurrencies, ",")
for _, s := range cryptoCurrencies {
_, err := strconv.Atoi(s)
if err != nil && common.StringContains(c.Cryptocurrencies, s) {
continue
} else {
return errors.New("RetrieveConfigCurrencyPairs: Incorrect Crypto-Currency")
}
}
for _, exchange := range c.Exchanges {
if exchange.Enabled {
baseCurrencies := common.SplitStrings(exchange.BaseCurrencies, ",")
enabledCurrencies := common.SplitStrings(exchange.EnabledPairs, ",")
for _, currencyPair := range enabledCurrencies {
ok, separator := currency.ContainsSeparator(currencyPair)
if ok {
pair := common.SplitStrings(currencyPair, separator)
for _, x := range pair {
ok, _ = currency.ContainsBaseCurrencyIndex(baseCurrencies, x)
if !ok {
cryptoCurrencies = currency.CheckAndAddCurrency(cryptoCurrencies, x)
}
}
} else {
ok, idx := currency.ContainsBaseCurrencyIndex(baseCurrencies, currencyPair)
if ok {
curr := strings.Replace(currencyPair, idx, "", -1)
if currency.ContainsBaseCurrency(baseCurrencies, curr) {
fiatCurrencies = currency.CheckAndAddCurrency(fiatCurrencies, curr)
} else {
cryptoCurrencies = currency.CheckAndAddCurrency(cryptoCurrencies, curr)
}
if currency.ContainsBaseCurrency(baseCurrencies, idx) {
fiatCurrencies = currency.CheckAndAddCurrency(fiatCurrencies, idx)
} else {
cryptoCurrencies = currency.CheckAndAddCurrency(cryptoCurrencies, idx)
}
}
}
}
}
}
currency.BaseCurrencies = common.JoinStrings(fiatCurrencies, ",")
if common.StringContains(currency.BaseCurrencies, "RUR") {
currency.BaseCurrencies = strings.Replace(currency.BaseCurrencies, "RUR", "RUB", -1)
}
c.Cryptocurrencies = common.JoinStrings(cryptoCurrencies, ",")
currency.CryptoCurrencies = c.Cryptocurrencies
return nil
}
// CheckConfig checks to see if there is an old configuration filename and path
// if found it will change it to correct filename.
func CheckConfig() error {
_, err := common.ReadFile(OldConfigFile)
if err == nil {
err = os.Rename(OldConfigFile, ConfigFile)
if err != nil {
return err
}
log.Printf(RenamingConfigFile+"\n", OldConfigFile, ConfigFile)
}
return nil
}
// ReadConfig verifies and checks for encryption and verifies the unencrypted
// file contains JSON.
func (c *Config) ReadConfig(configPath string) error {
var defaultPath string
if configPath == "" {
defaultPath = ConfigTestFile
} else {
defaultPath = configPath
}
err := CheckConfig()
if err != nil {
return err
}
file, err := common.ReadFile(defaultPath)
if err != nil {
return err
}
if !ConfirmECS(file) {
err = ConfirmConfigJSON(file, &c)
if err != nil {
return err
}
if c.EncryptConfig == configFileEncryptionDisabled {
return nil
}
if c.EncryptConfig == configFileEncryptionPrompt {
if c.PromptForConfigEncryption() {
c.EncryptConfig = configFileEncryptionEnabled
return c.SaveConfig("")
}
}
} else {
key, err := PromptForConfigKey()
if err != nil {
return err
}
data, err := DecryptConfigFile(file, key)
if err != nil {
return err
}
err = ConfirmConfigJSON(data, &c)
if err != nil {
return err
}
}
return nil
}
// SaveConfig saves your configuration to your desired path
func (c *Config) SaveConfig(configPath string) error {
var defaultPath string
if configPath == "" {
defaultPath = ConfigFile
} else {
defaultPath = configPath
}
payload, err := json.MarshalIndent(c, "", " ")
if c.EncryptConfig == configFileEncryptionEnabled {
key, err2 := PromptForConfigKey()
if err2 != nil {
return err
}
payload, err = EncryptConfigFile(payload, key)
if err != nil {
return err
}
}
err = common.WriteFile(defaultPath, payload)
if err != nil {
return err
}
return nil
}
// LoadConfig loads your configuration file into your configuration object
func (c *Config) LoadConfig(configPath string) error {
err := c.ReadConfig(configPath)
if err != nil {
return fmt.Errorf(ErrFailureOpeningConfig, ConfigFile, err)
}
err = c.CheckExchangeConfigValues()
if err != nil {
return fmt.Errorf(ErrCheckingConfigValues, err)
}
return nil
}
// GetConfig returns a pointer to a confiuration object
func GetConfig() *Config {
return &Cfg
}