def test_set_rpcuser(bitcoind_configuration: BitcoindConfiguration):
     bitcoind_configuration.file['rpcuser'] = '******'
     changed = ConfigurationFile(bitcoind_configuration.file.path)
     assert changed['rpcuser'] == 'test_user'
     bitcoind_configuration.file['rpcuser'] = '******'
     changed_again = ConfigurationFile(bitcoind_configuration.file.path)
     assert changed_again['rpcuser'] == 'test_user_2'
Exemple #2
0
    def test_read(self, configuration_file: ConfigurationFile):
        configuration: ConfigurationProperty = ConfigurationProperty(
            '1', 'key', 'value')
        configuration_file.save([configuration])
        lines = configuration_file.read()

        assert len(lines) == 1 and lines[0] == ('0', 'key', 'value')
Exemple #3
0
 def test_save(self, configuration_file: ConfigurationFile):
     test_value = 'test_value'
     configuration: ConfigurationProperty = ConfigurationProperty(
         '1', 'test_key', test_value)
     configuration_file.save([configuration])
     with open(configuration_file.path, 'r') as f:
         text = f.read()
         assert test_value in text
 def test_set_prune(bitcoind_configuration: BitcoindConfiguration):
     bitcoind_configuration.set_prune(True)
     pruned = ConfigurationFile(bitcoind_configuration.file.path)
     assert pruned['prune']
     assert not pruned['txindex']
     bitcoind_configuration.set_prune(False)
     unpruned = ConfigurationFile(bitcoind_configuration.file.path)
     assert not unpruned['prune']
     assert unpruned['txindex']
    def __init__(self, name: str, path: str, assign_op: str = '=', keys_info=None):
        super().__init__()

        self._name = name
        self._file = ConfigurationFile(path=path, assign_op=assign_op)
        self._configurations: List[ConfigurationProperty] = []
        self._keys_info = keys_info or {}

        self._last_identifier = 0

        self._save_disabled = False
Exemple #6
0
class Configuration(QObject):

    parameter_change = Signal(str, str, list)

    def __init__(self, name: str, path: str, assign_op: str = '='):
        super().__init__()
        self.name = name
        self.file = ConfigurationFile(path=path, assign_op=assign_op)
        self.cache = ConfigurationCache()
        self.lines = None

    def __getitem__(self, name):
        if self.cache[name] is not None and len(self.cache[name]) == 1:
            return self.cache[name][0]
        return self.cache[name]

    def __setitem__(self, key: str, new_value: Any) -> None:
        if not isinstance(new_value, list):
            new_value = [new_value]
        self.cache[key] = new_value

        string_values = self.stringify_values(new_value)
        self.lines = self.file.update(key, string_values)
        self.parameter_change.emit(self.name, key, string_values)

    @staticmethod
    def value_to_string(input_value: Any) -> str:
        if isinstance(input_value, str):
            string_value = input_value
        elif isinstance(input_value, bool):
            integer_value = int(input_value)
            string_value = str(integer_value)
        elif isinstance(input_value, int):
            string_value = str(input_value)
        else:
            raise NotImplementedError(
                f'value_to_string for {type(input_value)}')
        return string_value

    def stringify_values(self, new_values: List[Any]) -> List[str]:
        new_list = [self.value_to_string(iv) for iv in new_values]
        return new_list

    def load(self):
        self.lines = self.file.read()
        for key, value in self.lines:
            self[key] = value
 def load(self):
     file_name = 'torrc'
     tor_dir_path = TOR_DIR_PATH[OPERATING_SYSTEM]
     configuration_file_path = os.path.join(tor_dir_path, file_name)
     log.info('Loading tor configuration file',
              configuration_file_path=configuration_file_path)
     self.file = ConfigurationFile(path=configuration_file_path,
                                   assign_op=' ')
Exemple #8
0
    def test_parse_line(self, configuration_file: ConfigurationFile):
        test_vars = [['# comment', '', ''], ['key=value', 'key', 'value'],
                     ['key = value', 'key', 'value'],
                     [' key = value ', 'key', 'value'], ['=value', '', ''],
                     ['key=', '', ''], ['', '', '']]

        for test_var in test_vars:
            key, value = configuration_file.parse_line(test_var[0])
            print(key)
            print(value)
            assert key == test_var[1] and value == test_var[2]
 def test_assign_op(self, configuration_file: ConfigurationFile):
     configuration_file.update('key', ['value'])
     with open(configuration_file.path, 'r') as f:
         text = f.read()
         assert 'key=value' in text
     os.remove(configuration_file.path)
     new_object = ConfigurationFile(configuration_file.path, ' ')
     new_object.read()
     new_object.update('key', ['value'])
     with open(new_object.path, 'r') as f:
         text = f.read()
         assert 'key=value' not in text
         assert 'key value' in text
 def test_assign_op(self, configuration_file: ConfigurationFile):
     configuration_file['key'] = 'value'
     new_object = ConfigurationFile(configuration_file.path, ' ')
     with open(new_object.path, 'r') as f:
         text = f.read()
         assert 'key=value' in text
     new_object['key'] = 'value'
     with open(new_object.path, 'r') as f:
         text = f.read()
         assert 'key=value' not in text
         assert 'key value' in text
Exemple #11
0
 def test_assign_op(self, configuration_file: ConfigurationFile):
     configuration: ConfigurationProperty = ConfigurationProperty(
         '1', 'key', 'value')
     configuration_file.save([configuration])
     with open(configuration_file.path, 'r') as f:
         text = f.read()
         assert 'key=value' in text
     os.remove(configuration_file.path)
     new_configuration_file = ConfigurationFile(configuration_file.path,
                                                ' ')
     new_configuration_file.read()
     new_configuration_file.save([configuration])
     with open(new_configuration_file.path, 'r') as f:
         text = f.read()
         assert 'key=value' not in text
         assert 'key value' in text
class Configuration(QObject):

    configuration_changed = Signal(ConfigurationProperty, ConfigurationProperty)

    def __init__(self, name: str, path: str, assign_op: str = '=', keys_info=None):
        super().__init__()

        self._name = name
        self._file = ConfigurationFile(path=path, assign_op=assign_op)
        self._configurations: List[ConfigurationProperty] = []
        self._keys_info = keys_info or {}

        self._last_identifier = 0

        self._save_disabled = False

    @property
    def file(self):
        return self._file

    # Internal

    def _generate_identifier(self) -> str:
        self._last_identifier += 1
        return '_new_' + str(self._last_identifier)

    def _is_valid_configuration(self, name: str, value: str, default=True) -> bool:
        key_info = self._keys_info.get(name)
        if key_info is None:
            return default
        else:
            validators = key_info.get('validators')
            if not validators:
                return True
            return all([validator(value) for validator in validators])

    # File Management

    def load(self):
        self._save_disabled = True
        self._configurations = []
        for identifier, name, value in self._file.read():
            self.append_configuration(name, value, identifier)
        self._save_disabled = False

    def save(self):
        if not self._save_disabled:
            self.file.save(self._configurations)

    # Get

    def __contains__(self, item):
        for configuration in self._configurations:
            if configuration.name == item:
                return True

        return False

    def __getitem__(self, item):
        configurations = self.get_configurations_by_name(item)
        return configurations[0].value if configurations else None

    def get_all_configurations(self) -> List[ConfigurationProperty]:
        configurations = []
        for configuration in self._configurations:
            configurations.append(configuration.copy())

        return configurations

    def get_configurations_by_name(self, name: str) -> List[ConfigurationProperty]:
        configurations = []
        for configuration in self._configurations:
            if configuration.name == name:
                configurations.append(configuration.copy())

        return configurations

    def get_configuration_by_identifier(self, identifier: str) -> Optional[ConfigurationProperty]:
        for conf in self._configurations:
            if conf.identifier == identifier:
                return conf.copy()

        return None

    # Remove

    def __delitem__(self, key):
        return self.remove_configuration_by_name(key)

    def remove_configuration_by_name(self, name: str, signal: bool = True) -> List[ConfigurationProperty]:
        new_configurations: List[ConfigurationProperty] = []
        removed_configurations = []
        for configuration in self._configurations:
            if configuration.name != name:
                new_configurations.append(configuration)
            else:
                removed_configurations.append(configuration)

        self._configurations = new_configurations

        if signal:
            for configuration in removed_configurations:
                self.configuration_changed.emit(configuration, None)

        self.save()

        return removed_configurations

    def remove_configuration_by_identifier(self, identifier: str, signal: bool = True) -> Optional[ConfigurationProperty]:
        new_configurations: List[ConfigurationProperty] = []
        removed_configuration: ConfigurationProperty = None
        for configuration in self._configurations:
            if configuration.identifier != identifier:
                new_configurations.append(configuration)
            else:
                removed_configuration = configuration

        self._configurations = new_configurations

        if signal and removed_configuration:
            self.configuration_changed.emit(removed_configuration, None)

        self.save()

        return removed_configuration

    # Add / Modify

    def __setitem__(self, key, value):
        if isinstance(value, list):
            self.remove_configuration_by_name(key)
            added_configurations = []
            for val in value:
                added_configuration = self.append_configuration(key, val)
                if added_configuration is not None:
                    added_configurations.append(added_configuration)
            return added_configurations
        else:
            return self.replace_configuration(key, value)

    def set_default_configuration(self, name: str, value, signal: bool = True) -> List[ConfigurationProperty]:
        existing_configurations = self.get_configurations_by_name(name)

        if not existing_configurations:
            added_configuration = self.append_configuration(name, value)
            if added_configuration is not None:

                if signal:
                    self.configuration_changed.emit(None, added_configuration)

                self.save()

                return [added_configuration]

        return existing_configurations

    def edit_configuration(self, identifier: str, value, signal: bool = True) -> Optional[ConfigurationProperty]:
        for configuration in self._configurations:
            if configuration.identifier == identifier:

                if not self._is_valid_configuration(configuration.name, value):
                    return None

                old_configuration = configuration.copy()
                configuration.value = value

                if signal:
                    self.configuration_changed.emit(old_configuration, configuration)

                self.save()

                return configuration

        return None

    def replace_configuration(self, name: str, value, signal: bool = True) -> Optional[ConfigurationProperty]:

        removed_configurations = self.remove_configuration_by_name(name, signal=False)

        identifier = None
        if len(removed_configurations) > 0:
            identifier = removed_configurations[0].identifier

        added_configuration = self.append_configuration(name, value, identifier, signal=False)

        if added_configuration is not None:
            if signal:
                for i in range(len(removed_configurations)):
                    if i == 0:
                        self.configuration_changed.emit(removed_configurations[0], added_configuration)
                    else:
                        self.configuration_changed.emit(removed_configurations[i], None)
        else:
            for configuration in removed_configurations:
                self.append_configuration(configuration.name, configuration.value, configuration.identifier, False)

        self.save()

        return added_configuration

    def append_configuration(self, name: str, value, identifier=None, signal: bool = True) -> Optional[ConfigurationProperty]:

        if isinstance(value, bool):
            value = 1 if value else 0

        if isinstance(value, str):
            try:
                value = int(value)
            except ValueError:
                pass

        if not self._is_valid_configuration(name, value):
            return None

        if identifier is None:
            identifier = self._generate_identifier()

        configuration = ConfigurationProperty(identifier, name, value)
        self._configurations.append(configuration)

        if signal:
            self.configuration_changed.emit(None, configuration)

        self.save()

        return configuration
 def test_update(self, configuration_file: ConfigurationFile):
     configuration_file.update('test_attribute', [test_value])
     with open(configuration_file.path, 'r') as f:
         text = f.read()
         assert test_value in text
def configuration_file():
    with NamedTemporaryFile(suffix='.conf', delete=True) as f:
        name = f.name
    configuration_file = ConfigurationFile(name)
    configuration_file.read()
    return configuration_file
    def check(self):
        log.debug('datadir', datadir=self.file['datadir'])

        if (self.file['datadir'] is None
                or not os.path.exists(self.file['datadir'])):
            self.autoconfigure_datadir()

        if 'bitcoin.conf' in os.listdir(self.file['datadir']):
            actual_conf_file = os.path.join(self.file['datadir'],
                                            'bitcoin.conf')
            if self.file_path != actual_conf_file:
                log.info('datadir_redirect',
                         configuration_file_path=self.file_path,
                         actual_conf_file=actual_conf_file)
                self.file = ConfigurationFile(actual_conf_file)
                if (self.file['datadir'] is None
                        or not os.path.exists(self.file['datadir'])):
                    self.autoconfigure_datadir()

        if os.path.exists(os.path.join(self.file['datadir'], 'blocks')):
            if self.file['prune'] is None:
                self.set_prune(False)

        self.wallet_paths = self.get_wallet_paths()

        if self.file['server'] is None:
            self.file['server'] = True

        if self.file['disablewallet'] is None and not self.wallet_paths:
            self.file['disablewallet'] = True
        elif self.file['disablewallet'] is None and self.wallet_paths:
            self.file['disablewallet'] = False

        if self.file['timeout'] is None:
            self.file['timeout'] = 6000

        if self.file['rpcuser'] is None:
            self.file['rpcuser'] = '******'

        if self.file['rpcpassword'] is None:
            self.file['rpcpassword'] = get_random_password()

        if self.file['prune'] is None:
            should_prune = self.hard_drives.should_prune(self.file['datadir'],
                                                         has_bitcoin=True)
            self.set_prune(should_prune)

        self.zmq_block_port = get_zmq_port()
        self.zmq_tx_port = get_zmq_port()

        self.file['zmqpubrawblock'] = f'tcp://127.0.0.1:{self.zmq_block_port}'
        self.file['zmqpubrawtx'] = f'tcp://127.0.0.1:{self.zmq_tx_port}'

        self.file['proxy'] = '127.0.0.1:9050'
        self.file['listen'] = True
        self.file['bind'] = '127.0.0.1'
        self.file['debug'] = 'tor'
        self.file['discover'] = True

        # noinspection PyBroadException
        try:
            memory = psutil.virtual_memory()
            free_mb = round(memory.available / 1000000)
            free_mb -= int(free_mb * .3)
            self.file['dbcache'] = free_mb
        except:
            log.warning('dbcache psutil.virtual_memory', exc_info=True)
            self.file['dbcache'] = 1000

        self.config_snapshot = self.file.snapshot.copy()
 def load(self):
     log.info('bitcoin_configuration_file_path',
              configuration_file_path=self.file_path)
     self.file = ConfigurationFile(self.file_path)
class BitcoindConfiguration(object):
    file: ConfigurationFile
    hard_drives: HardDrives
    zmq_block_port: int
    zmq_tx_port: int

    def __init__(self):
        self.hard_drives = HardDrives()
        self.file = None
        file_name = 'bitcoin.conf'
        bitcoin_data_path = BITCOIN_DATA_PATH[OPERATING_SYSTEM]
        self.file_path = os.path.join(bitcoin_data_path, file_name)

    @property
    def args(self) -> List[str]:
        return [f'-conf={self.file_path}']

    @property
    def cli_args(self) -> List[str]:
        return self.args

    def load(self):
        log.info('bitcoin_configuration_file_path',
                 configuration_file_path=self.file_path)
        self.file = ConfigurationFile(self.file_path)

    def check(self):
        log.debug('datadir', datadir=self.file['datadir'])

        if (self.file['datadir'] is None
                or not os.path.exists(self.file['datadir'])):
            self.autoconfigure_datadir()

        if 'bitcoin.conf' in os.listdir(self.file['datadir']):
            actual_conf_file = os.path.join(self.file['datadir'],
                                            'bitcoin.conf')
            if self.file_path != actual_conf_file:
                log.info('datadir_redirect',
                         configuration_file_path=self.file_path,
                         actual_conf_file=actual_conf_file)
                self.file = ConfigurationFile(actual_conf_file)
                if (self.file['datadir'] is None
                        or not os.path.exists(self.file['datadir'])):
                    self.autoconfigure_datadir()

        if os.path.exists(os.path.join(self.file['datadir'], 'blocks')):
            if self.file['prune'] is None:
                self.set_prune(False)

        self.wallet_paths = self.get_wallet_paths()

        if self.file['server'] is None:
            self.file['server'] = True

        if self.file['disablewallet'] is None and not self.wallet_paths:
            self.file['disablewallet'] = True
        elif self.file['disablewallet'] is None and self.wallet_paths:
            self.file['disablewallet'] = False

        if self.file['timeout'] is None:
            self.file['timeout'] = 6000

        if self.file['rpcuser'] is None:
            self.file['rpcuser'] = '******'

        if self.file['rpcpassword'] is None:
            self.file['rpcpassword'] = get_random_password()

        if self.file['prune'] is None:
            should_prune = self.hard_drives.should_prune(self.file['datadir'],
                                                         has_bitcoin=True)
            self.set_prune(should_prune)

        self.zmq_block_port = get_zmq_port()
        self.zmq_tx_port = get_zmq_port()

        self.file['zmqpubrawblock'] = f'tcp://127.0.0.1:{self.zmq_block_port}'
        self.file['zmqpubrawtx'] = f'tcp://127.0.0.1:{self.zmq_tx_port}'

        self.file['proxy'] = '127.0.0.1:9050'
        self.file['listen'] = True
        self.file['bind'] = '127.0.0.1'
        self.file['debug'] = 'tor'
        self.file['discover'] = True

        # noinspection PyBroadException
        try:
            memory = psutil.virtual_memory()
            free_mb = round(memory.available / 1000000)
            free_mb -= int(free_mb * .3)
            self.file['dbcache'] = free_mb
        except:
            log.warning('dbcache psutil.virtual_memory', exc_info=True)
            self.file['dbcache'] = 1000

        self.config_snapshot = self.file.snapshot.copy()
        # self.file.file_watcher.fileChanged.connect(self.config_file_changed)

    def autoconfigure_datadir(self):
        default_datadir = BITCOIN_DATA_PATH[OPERATING_SYSTEM]
        big_drive = self.hard_drives.get_big_drive()
        default_is_big_enough = not self.hard_drives.should_prune(
            input_directory=default_datadir, has_bitcoin=True)
        default_is_biggest = self.hard_drives.is_default_partition(big_drive)
        log.info('autoconfigure_datadir',
                 default_is_big_enough=default_is_big_enough,
                 default_is_biggest=default_is_biggest)
        if default_is_big_enough or default_is_biggest:
            self.file['datadir'] = default_datadir
            log.info('autoconfigure_datadir', datadir=default_datadir)
            return

        if not self.hard_drives.should_prune(big_drive.mountpoint, False):
            datadir = os.path.join(big_drive.mountpoint, 'Bitcoin')
            self.file['datadir'] = datadir
            log.info('autoconfigure_datadir', datadir=datadir)
            if not os.path.exists(self.file['datadir']):
                os.mkdir(self.file['datadir'])
        else:
            self.file['datadir'] = default_datadir
            log.info('autoconfigure_datadir', datadir=default_datadir)

    def get_wallet_paths(self):
        exclude_files = {
            'addr.dat', 'banlist.dat', 'fee_estimates.dat', 'mempool.dat',
            'peers.dat'
        }
        candidate_paths = []
        datadir = self.file['datadir']
        wallet_dir = self.file['main.walletdir']
        wallets = self.file['main.wallet']
        for file in os.listdir(datadir):
            if file not in exclude_files:
                path = os.path.join(datadir, file)
                candidate_paths.append(path)
        default_walletdir = os.path.join(datadir, 'wallets')
        if os.path.exists(default_walletdir):
            for file in os.listdir(default_walletdir):
                if file not in exclude_files:
                    candidate_paths.append(
                        os.path.join(default_walletdir, file))
        if wallet_dir is not None:
            for file in os.listdir(wallet_dir):
                if file not in exclude_files:
                    candidate_paths += os.path.join(
                        os.path.join(wallet_dir, file))
        dat_files = [
            f for f in candidate_paths
            if f.endswith('.dat') and not f.startswith('blk')
        ]
        dat_files = set(dat_files)
        wallet_paths = set(dat_files - exclude_files)
        if wallets is not None:
            if isinstance(wallets, list):
                for wallet in wallets:
                    wallet_paths.add(wallet)
            else:
                wallet_paths.add(wallets)
        return wallet_paths

    @property
    def node_port(self):
        custom_port = self.file['main.port']
        if custom_port is not None:
            return custom_port
        return BITCOIN_MAINNET_PEER_PORT

    @property
    def rpc_port(self):
        custom_port = self.file['main.rpcport']
        if custom_port is not None:
            return custom_port
        return BITCOIN_MAINNET_RPC_PORT

    def set_prune(self, should_prune: bool = None):

        if should_prune is None:
            should_prune = self.hard_drives.should_prune(self.file['datadir'],
                                                         has_bitcoin=True)
        if should_prune:
            prune = MAINNET_PRUNE
            self.file['prune'] = prune
        else:
            self.file['prune'] = 0
        self.file['txindex'] = not should_prune

    def config_file_changed(self):
        # Refresh config file
        self.file.file_watcher.blockSignals(True)
        self.file.populate_cache()
        self.file.file_watcher.blockSignals(False)
        if self.file['zmqpubrawblock']:
            self.zmq_block_port = int(
                self.file['zmqpubrawblock'].split(':')[-1])
        if self.file['zmqpubrawtx']:
            self.zmq_tx_port = int(self.file['zmqpubrawtx'].split(':')[-1])
        # Some text editors do not modify the file, they delete and replace the file
        # Check if file is still in file_watcher list of files, if not add back
        files_watched = self.file.file_watcher.files()
        if len(files_watched) == 0:
            self.file.file_watcher.addPath(self.file.path)

    @property
    def restart_required(self):
        old_config = self.config_snapshot.copy()
        new_config = self.file.snapshot

        # First check that both config files are still on the same network
        old_config_network = 'testnet' in old_config.keys()
        new_config_network = 'testnet' in new_config.keys()

        if (old_config_network == new_config_network) and self.running:
            common_fields = [
                'rpcuser', 'rpcpassword', 'disablewallet', 'datadir',
                'disablewallet', 'zmqpubrawblock', 'zmqpubrawtx', 'prune',
                'txindex', 'timeout'
            ]

            for field in common_fields:

                # First check if field is found in both configs
                found_in_old_config = field in old_config.keys()
                found_in_new_config = field in new_config.keys()
                if found_in_old_config != found_in_new_config:
                    return True

                # Now check that values are the same
                if found_in_old_config:
                    if old_config[field] != new_config[field]:
                        return True

            else:
                # Only check mainnet fields if currently running mainnet
                mainnet_fields = ['rpcport', 'port']

                for field in mainnet_fields:
                    # First check if field is found in both configs
                    found_in_old_config = field in old_config.keys()
                    found_in_new_config = field in new_config.keys()
                    if found_in_old_config != found_in_new_config:
                        return True

                    # Now check that values are the same
                    if found_in_old_config:
                        if old_config[field] != new_config[field]:
                            return True

            return False
        elif self.running:
            # Network has changed and the node is running - Restart is required
            return True

        return False
class LndConfiguration(object):
    file: ConfigurationFile

    def __init__(self):
        file_name = 'lnd.conf'
        lnd_dir_path = LND_DIR_PATH[OPERATING_SYSTEM]
        self.file_path = os.path.join(lnd_dir_path, file_name)

    @property
    def args(self):
        if IS_WINDOWS:
            arg_list = [
                f'--configfile={self.file_path}',
            ]
        else:
            arg_list = [
                f'--configfile="{self.file_path}"',
            ]

        arg_list += ['--bitcoin.mainnet']
        return arg_list

    def cli_args(self) -> List[str]:
        args = []
        if self.grpc_port != LND_DEFAULT_GRPC_PORT:
            args.append(f'--rpcserver=127.0.0.1:{self.grpc_port}')
        if self.lnddir != LND_DIR_PATH[OPERATING_SYSTEM]:
            args.append(f'''--lnddir="{self.lnddir}"''')
            args.append(f'--macaroonpath="{self.macaroon_path}"')
            args.append(f'--tlscertpath="{self.tls_cert_path}"')
        return args

    def load(self):
        log.info('lnd configuration_file_path',
                 configuration_file_path=self.file_path)
        self.file = ConfigurationFile(self.file_path)

    def check(self):
        self.lnddir = LND_DIR_PATH[OPERATING_SYSTEM]

        # Previous versions of the launcher set lnddir in the config file,
        # but it is not a valid key so this helps old users upgrading
        if self.file['lnddir'] is not None:
            self.file['lnddir'] = None

        if self.file['debuglevel'] is None:
            self.file['debuglevel'] = 'info'

        self.file['bitcoin.active'] = True
        self.file['bitcoin.node'] = 'bitcoind'
        bitcoind_conf = BitcoindConfiguration()
        bitcoind_conf.load()
        self.file['bitcoind.rpchost'] = f'127.0.0.1:{bitcoind_conf.rpc_port}'
        self.file['bitcoind.rpcuser'] = bitcoind_conf.file['rpcuser']
        self.file['bitcoind.rpcpass'] = bitcoind_conf.file['rpcpassword']
        self.file['bitcoind.zmqpubrawblock'] = bitcoind_conf.file[
            'zmqpubrawblock']
        self.file['bitcoind.zmqpubrawtx'] = bitcoind_conf.file['zmqpubrawtx']

        if self.file['restlisten'] is None:
            self.rest_port = get_port(LND_DEFAULT_REST_PORT)
            self.file['restlisten'] = f'127.0.0.1:{self.rest_port}'
        else:
            self.rest_port = self.file['restlisten'].split(':')[-1]

        if not self.file['rpclisten']:
            self.grpc_port = get_port(LND_DEFAULT_GRPC_PORT)
            self.file['rpclisten'] = f'127.0.0.1:{self.grpc_port}'
        else:
            self.grpc_port = int(self.file['rpclisten'].split(':')[-1])

        if not self.file['tlsextraip']:
            self.file['tlsextraip'] = '127.0.0.1'

        if self.file['color'] is None:
            self.file['color'] = '#000000'

        self.file['tor.active'] = True
        self.file['tor.v3'] = True
        self.file['tor.streamisolation'] = True

        self.macaroon_path = os.path.join(self.lnddir, 'data', 'chain',
                                          'bitcoin', 'mainnet')
        self.config_snapshot = self.file.snapshot.copy()
        # self.file.file_watcher.fileChanged.connect(self.config_file_changed)
        # self.file.file_watcher.fileChanged.connect(
        #     self.bitcoin_config_file_changed)

        hostname_file = os.path.join(TOR_SERVICE_PATH, 'hostname')
        with open(hostname_file, 'r') as f:
            self.file['externalip'] = f.readline().strip()

    def config_file_changed(self):
        # Refresh config file
        self.file.file_watcher.blockSignals(True)
        self.file.populate_cache()
        self.file.file_watcher.blockSignals(False)
        if self.file['restlisten']:
            self.rest_port = int(self.file['restlisten'].split(':')[-1])
        if self.file['rpclisten']:
            self.grpc_port = int(self.file['rpclisten'].split(':')[-1])

        # Some text editors do not modify the file, they delete and replace the file
        # Check if file is still in file_watcher list of files, if not add back
        files_watched = self.file.file_watcher.files()
        if len(files_watched) == 0:
            self.file.file_watcher.addPath(self.file_path)

    def bitcoin_config_file_changed(self):
        # Refresh config file
        self.file.file_watcher.blockSignals(True)
        self.file.populate_cache()
        self.file.file_watcher.blockSignals(False)
        bitcoind_conf = BitcoindConfiguration()
        bitcoind_conf.load()
        self.file['bitcoind.rpchost'] = f'127.0.0.1:{bitcoind_conf.rpc_port}'
        self.file['bitcoind.rpcuser'] = bitcoind_conf.file['rpcuser']
        self.file['bitcoind.rpcpass'] = bitcoind_conf.file['rpcpassword']
        self.file['bitcoind.zmqpubrawblock'] = bitcoind_conf.file[
            'zmqpubrawblock']
        self.file['bitcoind.zmqpubrawtx'] = bitcoind_conf.file['zmqpubrawtx']

    @property
    def node_port(self) -> int:
        if self.file['listen'] is None:
            port = get_port(LND_DEFAULT_PEER_PORT)
            self.file['listen'] = f'127.0.0.1:{port}'
        else:
            if not isinstance(self.file['listen'], list):
                port = int(self.file['listen'].split(':')[-1])
            else:
                port = int(self.file['listen'][0].split(':')[-1])
        return port

    @property
    def admin_macaroon_path(self) -> str:
        path = os.path.join(self.macaroon_path, 'admin.macaroon')
        return path

    @property
    def wallet_path(self) -> str:
        wallet_path = os.path.join(self.macaroon_path, 'wallet.db')
        return wallet_path

    @property
    def has_wallet(self) -> bool:
        return os.path.isfile(self.wallet_path)

    @property
    def tls_cert_path(self) -> str:
        tls_cert_path = os.path.join(self.lnddir, 'tls.cert')
        return tls_cert_path

    @property
    def rest_url(self) -> str:
        return f'https://127.0.0.1:{self.rest_port}'

    @property
    def grpc_url(self) -> str:
        return f'127.0.0.1:{self.grpc_port}'

    @property
    def restart_required(self):
        if self.running:
            # Did bitcoin details change
            if self.restart_required:
                return True and self.running

            old_config = self.config_snapshot.copy()
            new_config = self.file.snapshot

            fields = ['restlisten', 'listen', 'rpclisten']

            for field in fields:
                # First check if field is found in both configs
                found_in_old_config = field in old_config.keys()
                found_in_new_config = field in new_config.keys()
                if found_in_old_config != found_in_new_config:
                    return True

                # Now check that values are the same
                if found_in_old_config:
                    if old_config[field] != new_config[field]:
                        return True

        return False

    @staticmethod
    def base64URL_from_base64(s):
        return s.replace('+', '-').replace('/', '_').rstrip('=')

    @property
    def lndconnect_mobile_url(self):
        host = self.grpc_url.split(':')[0]
        port = self.grpc_url.split(':')[1]
        with open(self.tls_cert_path, 'r') as cert_file:
            lines = cert_file.read().split('\n')
            lines = [line for line in lines if line != '']
            cert = ''.join(lines[1:-1])
            cert = self.base64URL_from_base64(cert)

        with open(self.admin_macaroon_path, 'rb') as macaroon_file:
            macaroon = base64.b64encode(macaroon_file.read()).decode('ascii')
            macaroon = self.base64URL_from_base64(macaroon)

        return f'lndconnect://{host}:{port}?cert={cert}&macaroon={macaroon}'

    @property
    def lndconnect_url(self):
        host = self.grpc_url.split(':')[0]
        port = self.grpc_url.split(':')[1]
        return f'lndconnect://{host}:{port}' \
            f'?cert={self.tls_cert_path}&macaroon={self.admin_macaroon_path}'

    @property
    def lndconnect_qrcode(self):
        img = qrcode.make(self.lndconnect_mobile_url)
        return img

    def test_tls_cert(self):
        context = ssl.create_default_context()
        context.load_verify_locations(cafile=self.tls_cert_path)
        conn = context.wrap_socket(socket.socket(socket.AF_INET),
                                   server_hostname='127.0.0.1')
        conn.connect(('127.0.0.1', int(self.rest_port)))
        cert = conn.getpeercert()
        return cert
 def test_getattr_bool(self, configuration_file: ConfigurationFile):
     configuration_file['test_bool_true'] = True
     configuration_file['test_bool_false'] = False
     new_object = ConfigurationFile(configuration_file.path)
     assert new_object['test_bool_true']
     assert not new_object['test_bool_false']
 def test_getattr(self, configuration_file: ConfigurationFile):
     configuration_file['test_attribute'] = test_value
     new_object = ConfigurationFile(configuration_file.path)
     assert new_object['test_attribute'] == test_value
 def __init__(self, name: str, path: str, assign_op: str = '='):
     super().__init__()
     self.name = name
     self.file = ConfigurationFile(path=path, assign_op=assign_op)
     self.cache = ConfigurationCache()
     self.lines = None