Browse Source

config: implement config upgrades. remove system config.

3.1
SomberNight 7 years ago
parent
commit
04a1809969
  1. 138
      lib/simple_config.py

138
lib/simple_config.py

@ -5,13 +5,11 @@ import os
import stat import stat
from copy import deepcopy from copy import deepcopy
from .util import (user_dir, print_error, print_stderr, PrintError, from .util import (user_dir, print_error, PrintError,
NoDynamicFeeEstimates) NoDynamicFeeEstimates)
from .bitcoin import MAX_FEE_RATE, FEE_TARGETS from .bitcoin import MAX_FEE_RATE, FEE_TARGETS
SYSTEM_CONFIG_PATH = "/etc/electrum.conf"
config = None config = None
@ -25,22 +23,26 @@ def set_config(c):
config = c config = c
FINAL_CONFIG_VERSION = 2
class SimpleConfig(PrintError): class SimpleConfig(PrintError):
""" """
The SimpleConfig class is responsible for handling operations involving The SimpleConfig class is responsible for handling operations involving
configuration files. configuration files.
There are 3 different sources of possible configuration values: There are two different sources of possible configuration values:
1. Command line options. 1. Command line options.
2. User configuration (in the user's config directory) 2. User configuration (in the user's config directory)
3. System configuration (in /etc/) They are taken in order (1. overrides config options set in 2.)
They are taken in order (1. overrides config options set in 2., that
override config set in 3.)
""" """
fee_rates = [5000, 10000, 20000, 30000, 50000, 70000, 100000, 150000, 200000, 300000] fee_rates = [5000, 10000, 20000, 30000, 50000, 70000, 100000, 150000, 200000, 300000]
def __init__(self, options={}, read_system_config_function=None, def __init__(self, options=None, read_user_config_function=None,
read_user_config_function=None, read_user_dir_function=None): read_user_dir_function=None):
if options is None:
options = {}
# This lock needs to be acquired for updating and reading the config in # This lock needs to be acquired for updating and reading the config in
# a thread-safe way. # a thread-safe way.
@ -52,8 +54,6 @@ class SimpleConfig(PrintError):
# The following two functions are there for dependency injection when # The following two functions are there for dependency injection when
# testing. # testing.
if read_system_config_function is None:
read_system_config_function = read_system_config
if read_user_config_function is None: if read_user_config_function is None:
read_user_config_function = read_user_config read_user_config_function = read_user_config
if read_user_dir_function is None: if read_user_dir_function is None:
@ -63,24 +63,30 @@ class SimpleConfig(PrintError):
# The command line options # The command line options
self.cmdline_options = deepcopy(options) self.cmdline_options = deepcopy(options)
# don't allow to be set on CLI:
# Portable wallets don't use a system config self.cmdline_options.pop('config_version', None)
if self.cmdline_options.get('portable', False):
self.system_config = {}
else:
self.system_config = read_system_config_function()
# Set self.path and read the user config # Set self.path and read the user config
self.user_config = {} # for self.get in electrum_path() self.user_config = {} # for self.get in electrum_path()
self.path = self.electrum_path() self.path = self.electrum_path()
self.user_config = read_user_config_function(self.path) self.user_config = read_user_config_function(self.path)
# Upgrade obsolete keys if not self.user_config:
self.fixup_keys({'auto_cycle': 'auto_connect'}) # avoid new config getting upgraded
self.user_config = {'config_version': FINAL_CONFIG_VERSION}
# config "upgrade" - CLI options
self.rename_config_keys(
self.cmdline_options, {'auto_cycle': 'auto_connect'}, True)
# config upgrade - user config
if self.requires_upgrade():
self.upgrade()
# Make a singleton instance of 'self' # Make a singleton instance of 'self'
set_config(self) set_config(self)
def electrum_path(self): def electrum_path(self):
# Read electrum_path from command line / system configuration # Read electrum_path from command line
# Otherwise use the user's default data directory. # Otherwise use the user's default data directory.
path = self.get('electrum_path') path = self.get('electrum_path')
if path is None: if path is None:
@ -102,45 +108,92 @@ class SimpleConfig(PrintError):
self.print_error("electrum directory", path) self.print_error("electrum directory", path)
return path return path
def fixup_config_keys(self, config, keypairs): def rename_config_keys(self, config, keypairs, deprecation_warning=False):
"""Migrate old key names to new ones"""
updated = False updated = False
for old_key, new_key in keypairs.items(): for old_key, new_key in keypairs.items():
if old_key in config: if old_key in config:
if not new_key in config: if new_key not in config:
config[new_key] = config[old_key] config[new_key] = config[old_key]
if deprecation_warning:
self.print_stderr('Note that the {} variable has been deprecated. '
'You should use {} instead.'.format(old_key, new_key))
del config[old_key] del config[old_key]
updated = True updated = True
return updated return updated
def fixup_keys(self, keypairs):
'''Migrate old key names to new ones'''
self.fixup_config_keys(self.cmdline_options, keypairs)
self.fixup_config_keys(self.system_config, keypairs)
if self.fixup_config_keys(self.user_config, keypairs):
self.save_user_config()
def set_key(self, key, value, save=True): def set_key(self, key, value, save=True):
if not self.is_modifiable(key): if not self.is_modifiable(key):
print_stderr("Warning: not changing config key '%s' set on the command line" % key) self.print_stderr("Warning: not changing config key '%s' set on the command line" % key)
return return
self._set_key_in_user_config(key, value, save)
def _set_key_in_user_config(self, key, value, save=True):
with self.lock: with self.lock:
if value is not None:
self.user_config[key] = value self.user_config[key] = value
else:
self.user_config.pop(key, None)
if save: if save:
self.save_user_config() self.save_user_config()
return
def get(self, key, default=None): def get(self, key, default=None):
with self.lock: with self.lock:
out = self.cmdline_options.get(key) out = self.cmdline_options.get(key)
if out is None: if out is None:
out = self.user_config.get(key) out = self.user_config.get(key, default)
if out is None:
out = self.system_config.get(key, default)
return out return out
def requires_upgrade(self):
return self.get_config_version() < FINAL_CONFIG_VERSION
def upgrade(self):
with self.lock:
self.print_error('upgrading config')
self.convert_version_2()
self.set_key('config_version', FINAL_CONFIG_VERSION, save=True)
def convert_version_2(self):
if not self._is_upgrade_method_needed(1, 1):
return
self.rename_config_keys(self.user_config, {'auto_cycle': 'auto_connect'})
try:
# migrate server string FROM host:port:proto TO host:port
server_str = self.user_config.get('server')
host, port, protocol = str(server_str).rsplit(':', 2)
assert protocol in ('s', 't')
int(port) # Throw if cannot be converted to int
server_str = str('{}:{}'.format(host, port))
self._set_key_in_user_config('server', server_str)
except BaseException:
self._set_key_in_user_config('server', None)
self.set_key('config_version', 2)
def _is_upgrade_method_needed(self, min_version, max_version):
cur_version = self.get_config_version()
if cur_version > max_version:
return False
elif cur_version < min_version:
raise BaseException(
('config upgrade: unexpected version %d (should be %d-%d)'
% (cur_version, min_version, max_version)))
else:
return True
def get_config_version(self):
config_version = self.get('config_version', 1)
if config_version > FINAL_CONFIG_VERSION:
self.print_stderr('WARNING: config version ({}) is higher than ours ({})'
.format(config_version, FINAL_CONFIG_VERSION))
return config_version
def is_modifiable(self, key): def is_modifiable(self, key):
return not key in self.cmdline_options return key not in self.cmdline_options
def save_user_config(self): def save_user_config(self):
if not self.path: if not self.path:
@ -298,21 +351,6 @@ class SimpleConfig(PrintError):
return device return device
def read_system_config(path=SYSTEM_CONFIG_PATH):
"""Parse and return the system config settings in /etc/electrum.conf."""
result = {}
if os.path.exists(path):
import configparser
p = configparser.ConfigParser()
try:
p.read(path)
for k, v in p.items('client'):
result[k] = v
except (configparser.NoSectionError, configparser.MissingSectionHeaderError):
pass
return result
def read_user_config(path): def read_user_config(path):
"""Parse and store the user config settings in electrum.conf into user_config[].""" """Parse and store the user config settings in electrum.conf into user_config[]."""
if not path: if not path:

Loading…
Cancel
Save