def setupUi(self): ui_send_payout_dlg.Ui_SendPayoutDlg.setupUi(self, self) assert isinstance(self.tableView, QTableView) self.resize(cache.get_value('WndPayoutWidth', 800, int), cache.get_value('WndPayoutHeight', 460, int)) self.setWindowTitle('Transfer funds') self.closeEvent = self.closeEvent self.chbHideCollateralTx.setChecked(True) self.edtDestAddress.setText( cache.get_value('WndPayoutPaymentAddress', '', str)) self.edtDestAddress.textChanged.connect(self.edtDestAddressChanged) self.setIcon(self.btnCheckAll, 'check.png') self.setIcon(self.btnUncheckAll, 'uncheck.png') self.table_model = PaymentTableModel( None, self.chbHideCollateralTx.isChecked(), self.onUtxoCheckChanged, self) self.tableView.setModel(self.table_model) self.tableView.horizontalHeader().resizeSection(0, 35) self.tableView.horizontalHeader().setSectionResizeMode( 0, QHeaderView.Fixed) self.tableView.verticalHeader().setDefaultSectionSize( self.tableView.verticalHeader().fontMetrics().height() + 6) # set utxo table default column widths cws = cache.get_value('WndPayoutColWidths', self.table_model.getDefaultColWidths(), list) for col, w in enumerate(cws): self.tableView.setColumnWidth(col, w) self.chbHideCollateralTx.toggled.connect( self.chbHideCollateralTxToggled) self.resizeEvent = self.resizeEvent if self.main_ui.config.hw_type == HWType.ledger_nano_s: self.pnlSourceAddress.setVisible(False) self.org_message = '<span style="color:red">Sending funds controlled by Ledger Nano S wallets is not ' \ 'supported yet.</span>' self.setMessage(self.org_message) self.load_utxos() self.btnSend.setEnabled(False) elif len(self.utxos_source): self.pnlSourceAddress.setVisible(False) self.org_message = 'List of Unspent Transaction Outputs <i>(UTXOs)</i> for specified address(es). ' \ 'Select checkboxes for the UTXOs you wish to transfer.' self.setMessage(self.org_message) self.load_utxos() else: self.setMessage("") self.source_address_mode = True self.edtSourceBip32Path.setText( cache.get_value('SourceBip32Path', '', str))
def import_connections(self, in_conns, force_import): """ Imports connections from a list. Used at the app's start to process default connections and/or from a configuration dialog, when user pastes from a clipboard a string, describing connections he wants to add to the configuration. The latter feature is used for a convenience. :param in_conns: list of DashNetworkConnectionCfg objects. :returns: tuple (list_of_added_connections, list_of_updated_connections) """ added_conns = [] updated_conns = [] if in_conns: for nc in in_conns: id = nc.get_conn_id() # check if new connection is in existing list conn = self.get_conn_cfg_by_id(id) if not conn: if force_import or not cache.get_value('imported_default_conn_' + nc.get_conn_id(), False, bool): # this new connection was not automatically imported before self.dash_net_configs.append(nc) added_conns.append(nc) cache.set_value('imported_default_conn_' + nc.get_conn_id(), True) elif not conn.identical(nc) and force_import: conn.copy_from(nc) updated_conns.append(conn) return added_conns, updated_conns
def setupUi(self): Ui_TransactionDlg.setupUi(self, self) self.setWindowTitle('Transaction') self.chb_word_wrap.setChecked( app_cache.get_value(CACHE_ITEM_DETAILS_WORD_WRAP, False, bool)) self.apply_word_wrap(self.chb_word_wrap.isChecked()) self.edt_recipients.setOpenExternalLinks(True) self.edt_recipients.viewport().setAutoFillBackground(False) if sys.platform == 'win32': self.base_font_size = '8' self.title_font_size = '12' elif sys.platform == 'linux': self.base_font_size = '8' self.title_font_size = '14' else: # mac self.base_font_size = '10' self.title_font_size = '17' self.edt_raw_transaction.setStyleSheet( f'font: {self.base_font_size}pt "Courier New";') doc = QTextDocument(self) doc.setDocumentMargin(0) doc.setHtml( f'<span style=" font-size:{self.title_font_size}pt;white-space:nowrap">AAAAAAAAAAAAAAAAAA' ) default_width = int(doc.size().width()) * 3 default_height = int(default_width / 2) app_cache.restore_window_size(self, default_width=default_width, default_height=default_height) self.prepare_tx_view()
def setupUi(self): Ui_TransactionDlg.setupUi(self, self) self.setWindowTitle('Transaction') self.chb_word_wrap.setChecked(app_cache.get_value(CACHE_ITEM_DETAILS_WORD_WRAP, False, bool)) self.apply_word_wrap(self.chb_word_wrap.isChecked()) self.edt_recipients.setOpenExternalLinks(True) self.edt_recipients.viewport().setAutoFillBackground(False) self.prepare_tx_view()
def setupUi(self): ui_send_payout_dlg.Ui_SendPayoutDlg.setupUi(self, self) assert isinstance(self.tableView, QTableView) self.resize(cache.get_value('WndPayoutWidth', 800, int), cache.get_value('WndPayoutHeight', 460, int)) self.setWindowTitle('Transfer funds') self.closeEvent = self.closeEvent self.chbHideCollateralTx.setChecked(True) self.btnClose.clicked.connect(self.btnCloseClick) self.btnSend.clicked.connect(self.btnSendClick) self.edtDestAddress.setText( cache.get_value('WndPayoutPaymentAddress', '', str)) self.edtDestAddress.textChanged.connect(self.edtDestAddressChanged) self.setIcon(self.btnCheckAll, 'check.png') self.setIcon(self.btnUncheckAll, 'uncheck.png') self.table_model = PaymentTableModel( None, self.chbHideCollateralTx.isChecked(), self.onUtxoCheckChanged) self.tableView.setModel(self.table_model) self.tableView.horizontalHeader().resizeSection(0, 35) self.tableView.horizontalHeader().setSectionResizeMode( 0, QHeaderView.Fixed) self.tableView.verticalHeader().setDefaultSectionSize( self.tableView.verticalHeader().fontMetrics().height() + 6) # set utxo table default column widths cws = cache.get_value('WndPayoutColWidths', self.table_model.getDefaultColWidths(), list) for col, w in enumerate(cws): self.tableView.setColumnWidth(col, w) self.chbHideCollateralTx.toggled.connect( self.chbHideCollateralTxToggled) self.resizeEvent = self.resizeEvent self.threadFunctionDialog(self.load_utxos_thread, (), True, center_by_window=self.main_ui) self.table_model.setUtxos(self.utxos, self.masternodes)
def restore_col_defs(self, setting_name: str): cols = app_cache.get_value(setting_name, [], list) if cols: idx = 0 for _c in cols: name = _c.get('name') c = self.col_by_name(name) if c: c.visual_index = idx c.visible = _c.get('visible', True) c.initial_width = _c.get('width', c.initial_width) idx += 1 self._columns.sort(key=lambda x: x.visual_index) self._rebuild_column_index()
def restore_col_defs(self, setting_name: str) -> bool: """ :return: True, if columns settings were found in cache, False otherwise """ cols = app_cache.get_value(setting_name, [], list) if cols: idx = 0 for _c in cols: name = _c.get('name') c = self.col_by_name(name) if c: c.visual_index = idx c.visible = _c.get('visible', True) c.initial_width = _c.get('width', c.initial_width) idx += 1 self._columns.sort(key=lambda x: x.visual_index) self._rebuild_column_index() return True return False
def init(self, app_path): """ Initialize configuration after openning the application. """ self.app_path = app_path try: with open(os.path.join(app_path, 'version.txt')) as fptr: lines = fptr.read().splitlines() self.app_version = app_utils.extract_app_version(lines) except: pass parser = argparse.ArgumentParser() parser.add_argument('--config', help="Path to a configuration file", dest='config') parser.add_argument('--data-dir', help="Root directory for configuration file, cache and log dubdirs", dest='data_dir') args = parser.parse_args() app_user_dir = '' if args.data_dir: if os.path.exists(args.data_dir): if os.path.isdir(args.data_dir): app_user_dir = args.data_dir else: WndUtils.errorMsg('--data-dir parameter doesn\'t point to a directory. Using the default ' 'data directory.') else: WndUtils.errorMsg('--data-dir parameter doesn\'t point to an existing directory. Using the default ' 'data directory.') if not app_user_dir: home_dir = expanduser('~') app_user_dir = os.path.join(home_dir, APP_NAME_SHORT) if not os.path.exists(app_user_dir): os.makedirs(app_user_dir) self.cache_dir = os.path.join(app_user_dir, 'cache') if not os.path.exists(self.cache_dir): os.makedirs(self.cache_dir) cache.init(self.cache_dir, self.app_version) self.app_last_version = cache.get_value('app_version', '', str) self.app_config_file_name = '' if args.config is not None: self.app_config_file_name = args.config if not os.path.exists(self.app_config_file_name): msg = 'Config file "%s" does not exist.' % self.app_config_file_name print(msg) raise Exception(msg) if not self.app_config_file_name: self.app_config_file_name = os.path.join(app_user_dir, 'config.ini') # setup logging self.log_dir = os.path.join(app_user_dir, 'logs') self.log_file = os.path.join(self.log_dir, 'dmt.log') if not os.path.exists(self.log_dir): os.makedirs(self.log_dir) self.log_level_str = 'INFO' log_exists = os.path.exists(self.log_file) handler = RotatingFileHandler(filename=self.log_file, mode='a', backupCount=30) logger = logging.getLogger() formatter = logging.Formatter(fmt='%(asctime)s %(levelname)s |%(threadName)s |%(filename)s |%(funcName)s ' '|%(message)s', datefmt='%Y-%m-%d %H:%M:%S') handler.setFormatter(formatter) logger.addHandler(handler) logger.setLevel(self.log_level_str) if log_exists: handler.doRollover() logging.info('App started') # database (SQLITE) cache for caching bigger datasets: self.db_cache_file_name = os.path.join(self.cache_dir, 'dmt_cache.db') try: self.db_intf = DBCache(self.db_cache_file_name) except Exception as e: logging.exception('SQLite initialization error') # directory for configuration backups: self.cfg_backup_dir = os.path.join(app_user_dir, 'backup') if not os.path.exists(self.cfg_backup_dir): os.makedirs(self.cfg_backup_dir) try: # read configuration from a file self.read_from_file() except: pass if not self.app_last_version or \ app_utils.version_str_to_number(self.app_last_version) < app_utils.version_str_to_number(self.app_version): cache.save_data() self.initialized = True
def restore_cache_settings(self): app_cache.restore_window_size(self) self.show_manual_commands = app_cache.get_value( CACHE_ITEM_SHOW_COMMANDS, False, bool)
def restore_cache_settings(self): app_cache.restore_window_size(self) if self.initial_mn_sel is None: mode = app_cache.get_value(CACHE_ITEM_UTXO_SOURCE_MODE, 2, int) if mode in (1, 2, 3): self.utxo_src_mode = mode # activated bip32 account numbers: nrs = app_cache.get_value(CACHE_ITEM_HW_ACCOUNT_NUMBERS, [0, 1, 2, 3, 4], list) nrs.sort() self.hw_account_numbers.clear() self.hw_account_numbers.extend(nrs) # base bip32 path: path = app_cache.get_value(CACHE_ITEM_HW_ACCOUNT_BASE_PATH.replace('%NETWORK%', self.app_config.gobyte_network), gobyte_utils.get_default_bip32_base_path(self.app_config.gobyte_network), str) if gobyte_utils.validate_bip32_path(path): self.hw_account_base_bip32_path = path else: self.hw_account_base_bip32_path = gobyte_utils.get_default_bip32_base_path(self.app_config.gobyte_network) # account number: nr = app_cache.get_value(CACHE_ITEM_HW_ACCOUNT_NUMBER, 0, int) if nr < 0: nr = 0 self.hw_account_number = nr # bip32 path (utxo_src_mode 3) path = app_cache.get_value(CACHE_ITEM_HW_SRC_BIP32_PATH.replace('%NETWORK%', self.app_config.gobyte_network), gobyte_utils.get_default_bip32_path(self.app_config.gobyte_network), str) if gobyte_utils.validate_bip32_path(path): self.hw_src_bip32_path = path else: self.hw_src_bip32_path = gobyte_utils.get_default_bip32_path(self.app_config.gobyte_network) self.grid_column_widths = app_cache.get_value(CACHE_ITEM_COL_WIDTHS, self.table_model.getDefaultColWidths(), list) sel_nasternode = app_cache.get_value( CACHE_ITEM_UTXO_SRC_MASTRNODE.replace('%NETWORK%', self.app_config.gobyte_network), '', str) if sel_nasternode: if sel_nasternode == '<ALL>': self.mn_src_index = len(self.masternodes) else: for idx, mn in enumerate(self.masternodes): if mn.name == sel_nasternode: self.mn_src_index = idx break # restore last list of used addresses enc_json_str = app_cache.get_value(CACHE_ITEM_LAST_RECIPIENTS.replace('%NETWORK%', self.app_config.gobyte_network), None, str) if enc_json_str: try: # hw encryption key may be not available so use the generated key to not save addresses as plain text self.encryption_key = base64.urlsafe_b64encode(self.app_config.hw_generated_key) fernet = Fernet(self.encryption_key) enc_json_str = bytes(enc_json_str, 'ascii') json_str = fernet.decrypt(enc_json_str) json_str = json_str.decode('ascii') self.recipient_list_from_cache = simplejson.loads(json_str) except Exception: logging.exception('Cannot restore data from cache.')
def setupUi(self): Ui_ConfigDlg.setupUi(self, self) self.resize( app_cache.get_value('ConfigDlg_Width', self.size().width(), int), app_cache.get_value('ConfigDlg_Height', self.size().height(), int)) self.setWindowTitle("Configuration") self.splitter.setStretchFactor(0, 0) self.splitter.setStretchFactor(1, 1) self.accepted.connect(self.on_accepted) self.tabWidget.setCurrentIndex(0) self.disable_cfg_update = True layout_details = self.detailsFrame.layout() self.chbConnEnabled = QCheckBox("Enabled") self.chbConnEnabled.toggled.connect(self.on_chbConnEnabled_toggled) layout_details.addWidget(self.chbConnEnabled) self.chbUseSshTunnel = QCheckBox("Use SSH tunnel") self.chbUseSshTunnel.toggled.connect(self.on_chbUseSshTunnel_toggled) layout_details.addWidget(self.chbUseSshTunnel) self.ssh_tunnel_widget = SshConnectionWidget(self) layout_details.addWidget(self.ssh_tunnel_widget) # layout for button for reading RPC configuration from remote host over SSH: hl = QHBoxLayout() self.btnSshReadRpcConfig = QPushButton( "\u2193 Read RPC configuration from SSH host \u2193") self.btnSshReadRpcConfig.clicked.connect( self.on_btnSshReadRpcConfig_clicked) hl.addWidget(self.btnSshReadRpcConfig) hl.addStretch() layout_details.addLayout(hl) # add connection-editing controls widget: self.rpc_cfg_widget = RpcConnectionWidget(self.detailsFrame) layout_details.addWidget(self.rpc_cfg_widget) # layout for controls related to setting up an additional encryption hl = QHBoxLayout() self.btnEncryptionPublicKey = QPushButton("RPC encryption public key") self.btnEncryptionPublicKey.clicked.connect( self.on_btnEncryptionPublicKey_clicked) hl.addWidget(self.btnEncryptionPublicKey) self.lblEncryptionPublicKey = QLabel(self) self.lblEncryptionPublicKey.setText('') hl.addWidget(self.lblEncryptionPublicKey) hl.addStretch() layout_details.addLayout(hl) # layout for the 'test connection' button: hl = QHBoxLayout() self.btnTestConnection = QPushButton("\u2713 Test connection") self.btnTestConnection.clicked.connect( self.on_btnTestConnection_clicked) sp = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) sp.setHorizontalStretch(0) sp.setVerticalStretch(0) sp.setHeightForWidth( self.btnTestConnection.sizePolicy().hasHeightForWidth()) self.btnTestConnection.setSizePolicy(sp) hl.addWidget(self.btnTestConnection) hl.addStretch() layout_details.addLayout(hl) layout_details.addStretch() self.rpc_cfg_widget.edtRpcHost.textEdited.connect( self.on_edtRpcHost_textEdited) self.rpc_cfg_widget.edtRpcPort.textEdited.connect( self.on_edtRpcPort_textEdited) self.rpc_cfg_widget.edtRpcUsername.textEdited.connect( self.on_edtRpcUsername_textEdited) self.rpc_cfg_widget.edtRpcPassword.textEdited.connect( self.on_edtRpcPassword_textEdited) self.rpc_cfg_widget.chbRpcSSL.toggled.connect(self.chbRpcSSL_toggled) self.ssh_tunnel_widget.edtSshHost.textEdited.connect( self.on_edtSshHost_textEdited) self.ssh_tunnel_widget.edtSshPort.textEdited.connect( self.on_edtSshPort_textEdited) self.ssh_tunnel_widget.edtSshUsername.textEdited.connect( self.on_edtSshUsername_textEdited) self.ssh_tunnel_widget.cboAuthentication.currentIndexChanged.connect( self.on_cboSshAuthentication_currentIndexChanged) self.ssh_tunnel_widget.edtPrivateKeyPath.textChanged.connect( self.on_edtSshPrivateKeyPath_textChanged) self.lstConns.setContextMenuPolicy(Qt.CustomContextMenu) self.popMenu = QMenu(self) self.action_new_connection = self.popMenu.addAction( "Add new connection") self.action_new_connection.triggered.connect( self.on_action_new_connection_triggered) self.setIcon(self.action_new_connection, '*****@*****.**') self.btnNewConn.setDefaultAction(self.action_new_connection) self.action_delete_connections = self.popMenu.addAction( "Delete selected connection(s)") self.action_delete_connections.triggered.connect( self.on_action_delete_connections_triggered) self.setIcon(self.action_delete_connections, '*****@*****.**') self.btnDeleteConn.setDefaultAction(self.action_delete_connections) self.action_copy_connections = self.popMenu.addAction( "Copy connection(s) to clipboard", self.on_action_copy_connections_triggered, QKeySequence("Ctrl+C")) self.setIcon(self.action_copy_connections, '*****@*****.**') self.addAction(self.action_copy_connections) self.action_paste_connections = self.popMenu.addAction( "Paste connection(s) from clipboard", self.on_action_paste_connections_triggered, QKeySequence("Ctrl+V")) self.setIcon(self.action_paste_connections, '*****@*****.**') self.addAction(self.action_paste_connections) self.btnNewConn.setText("") self.btnDeleteConn.setText("") self.btnMoveDownConn.setText("") self.btnMoveUpConn.setText("") self.btnRestoreDefault.setText("") self.setIcon(self.btnMoveDownConn, "*****@*****.**") self.setIcon(self.btnMoveUpConn, "*****@*****.**", rotate=180) self.setIcon(self.btnRestoreDefault, "*****@*****.**") self.setIcon(self.rpc_cfg_widget.btnShowPassword, "*****@*****.**") self.rpc_cfg_widget.btnShowPassword.setText("") self.rpc_cfg_widget.btnShowPassword.pressed.connect( lambda: self.rpc_cfg_widget.edtRpcPassword.setEchoMode(QLineEdit. Normal)) self.rpc_cfg_widget.btnShowPassword.released.connect( lambda: self.rpc_cfg_widget.edtRpcPassword.setEchoMode(QLineEdit. Password)) if self.local_config.is_mainnet(): self.cboDashNetwork.setCurrentIndex(0) self.connections_current = self.connections_mainnet else: self.cboDashNetwork.setCurrentIndex(1) self.connections_current = self.connections_testnet for cfg in self.local_config.dash_net_configs: if cfg.testnet: self.connections_testnet.append(cfg) else: self.connections_mainnet.append(cfg) if self.local_config.hw_type == HWType.trezor: self.chbHwTrezor.setChecked(True) elif self.local_config.hw_type == HWType.keepkey: self.chbHwKeepKey.setChecked(True) else: self.chbHwLedgerNanoS.setChecked(True) if self.local_config.hw_keepkey_psw_encoding == 'NFC': self.cboKeepkeyPassEncoding.setCurrentIndex(0) else: self.cboKeepkeyPassEncoding.setCurrentIndex(1) note_url = get_note_url('DMTN0001') self.lblKeepkeyPassEncoding.setText( f'KepKey passphrase encoding (<a href="{note_url}">see</a>)') self.chbCheckForUpdates.setChecked(self.local_config.check_for_updates) self.chbBackupConfigFile.setChecked( self.local_config.backup_config_file) self.chbDownloadProposalExternalData.setChecked( self.local_config.read_proposals_external_attributes) self.chbDontUseFileDialogs.setChecked( self.local_config.dont_use_file_dialogs) self.chbConfirmWhenVoting.setChecked( self.local_config.confirm_when_voting) self.chbAddRandomOffsetToVotingTime.setChecked( self.local_config.add_random_offset_to_vote_time) self.chbEncryptConfigFile.setChecked( self.local_config.encrypt_config_file) idx = { 'CRITICAL': 0, 'ERROR': 1, 'WARNING': 2, 'INFO': 3, 'DEBUG': 4, 'NOTSET': 5 }.get(self.local_config.log_level_str, 2) self.cboLogLevel.setCurrentIndex(idx) self.display_connection_list() if len(self.local_config.dash_net_configs): self.lstConns.setCurrentRow(0) self.update_keepkey_pass_encoding_ui() self.update_connection_details_ui() self.disable_cfg_update = False self.splitter.setSizes( app_cache.get_value('ConfigDlg_ConnectionSplitter_Sizes', [100, 100], list))
def get_cache_value(self, name, default_value, type): return app_cache.get_value(self.__class__.__name__ + '_' + name, default_value, type)
def get_ghostnodelist(self, *args, data_max_age=MASTERNODES_CACHE_VALID_SECONDS): """ Returns masternode list, read from the Dash network or from the internal cache. :param args: arguments passed to the 'ghostnodelist' RPC call :param data_max_age: maximum age (in seconds) of the cached masternode data to used; if the cache is older than 'data_max_age', then an RPC call is performed to load newer masternode data; value of 0 forces reading of the new data from the network :return: list of Masternode objects, matching the 'args' arguments """ def parse_mns(mns_raw): """ Parses dictionary of strings returned from the RPC to Masternode object list. :param mns_raw: Dict of masternodes in format of RPC ghostnodelist command :return: list of Masternode object """ tm_begin = time.time() ret_list = [] for mn_id in mns_raw.keys(): mn_raw = mns_raw.get(mn_id) mn_raw = mn_raw.strip() elems = mn_raw.split() if len(elems) >= 8: mn = Masternode() # (status, protocol, payee, lastseen, activeseconds, lastpaidtime, pastpaidblock, ip) mn.status, mn.protocol, mn.payee, mn.lastseen, mn.activeseconds, mn.lastpaidtime, \ mn.lastpaidblock, mn.ip = elems mn.lastseen = int(mn.lastseen) mn.activeseconds = int(mn.activeseconds) mn.lastpaidtime = int(mn.lastpaidtime) mn.lastpaidblock = int(mn.lastpaidblock) mn.ident = mn_id ret_list.append(mn) duration = time.time() - tm_begin logging.info('Parse ghostnodelist time: ' + str(duration)) return ret_list def update_masternode_data(existing_mn, new_data, cursor): # update cached masternode's properties existing_mn.modified = False existing_mn.monitor_changes = True existing_mn.ident = new_data.ident existing_mn.status = new_data.status existing_mn.protocol = new_data.protocol existing_mn.payee = new_data.payee existing_mn.lastseen = new_data.lastseen existing_mn.activeseconds = new_data.activeseconds existing_mn.lastpaidtime = new_data.lastpaidtime existing_mn.lastpaidblock = new_data.lastpaidblock existing_mn.ip = new_data.ip # ... and finally update MN db record if cursor and existing_mn.modified: cursor.execute("UPDATE MASTERNODES set ident=?, status=?, protocol=?, payee=?," " last_seen=?, active_seconds=?, last_paid_time=?, " " last_paid_block=?, ip=?" "WHERE id=?", (new_data.ident, new_data.status, new_data.protocol, new_data.payee, new_data.lastseen, new_data.activeseconds, new_data.lastpaidtime, new_data.lastpaidblock, new_data.ip, existing_mn.db_id)) if self.open(): if len(args) == 1 and args[0] == 'full': last_read_time = app_cache.get_value(f'MasternodesLastReadTime_{self.app_config.dash_network}', 0, int) logging.info("MasternodesLastReadTime: %d" % last_read_time) if self.masternodes and data_max_age > 0 and \ int(time.time()) - last_read_time < data_max_age: logging.info('Using cached ghostnodelist (data age: %s)' % str(int(time.time()) - last_read_time)) return self.masternodes else: logging.info('Loading masternode list from NIX daemon...') mns = self.proxy.ghostnodelist(*args) mns = parse_mns(mns) logging.info('Finished loading masternode list') # mark already cached masternodes to identify those to delete for mn in self.masternodes: mn.marker = False # save masternodes to the db cache db_modified = False cur = None try: if self.db_intf.db_active: cur = self.db_intf.get_cursor() for mn in mns: # check if newly-read masternode already exists in the cache existing_mn = self.masternodes_by_ident.get(mn.ident) if not existing_mn: mn.marker = True self.masternodes.append(mn) self.masternodes_by_ident[mn.ident] = mn if self.db_intf.db_active: cur.execute("INSERT INTO MASTERNODES(ident, status, protocol, payee, last_seen," " active_seconds, last_paid_time, last_paid_block, ip, dmt_active," " dmt_create_time) " "VALUES (?,?,?,?,?,?,?,?,?,?,?)", (mn.ident, mn.status, mn.protocol, mn.payee, mn.lastseen, mn.activeseconds, mn.lastpaidtime, mn.lastpaidblock, mn.ip, 1, datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'))) mn.db_id = cur.lastrowid db_modified = True else: existing_mn.marker = True update_masternode_data(existing_mn, mn, cur) db_modified = True # remove from the cache masternodes that no longer exist for mn_index in reversed(range(len(self.masternodes))): mn = self.masternodes[mn_index] if not mn.marker: if self.db_intf.db_active: cur.execute("UPDATE MASTERNODES set dmt_active=0, dmt_deactivation_time=?" "WHERE ID=?", (datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), mn.db_id)) db_modified = True self.masternodes_by_ident.pop(mn.ident,0) del self.masternodes[mn_index] app_cache.set_value(f'MasternodesLastReadTime_{self.app_config.dash_network}', int(time.time())) self.update_mn_queue_values() finally: if db_modified: self.db_intf.commit() if cur is not None: self.db_intf.release_cursor() return self.masternodes else: mns = self.proxy.ghostnodelist(*args) mns = parse_mns(mns) return mns else: raise Exception('Not connected')
def restore_cache_settings(self): app_cache.restore_window_size(self) self.show_field_hinds = app_cache.get_value( CACHE_ITEM_SHOW_FIELD_HINTS, True, bool)
def setupUi(self, Form): self.setSizePolicy( QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)) self.lay_main = QtWidgets.QVBoxLayout(Form) self.lay_main.setContentsMargins(6, 6, 6, 6) self.lay_main.setSpacing(3) # 'totals' area: self.lbl_totals = QLabel(Form) self.lbl_totals.setTextInteractionFlags( QtCore.Qt.LinksAccessibleByMouse | QtCore.Qt.TextSelectableByMouse) self.lay_main.addWidget(self.lbl_totals) # output definition data file labels: self.lay_data_file = QHBoxLayout() self.lay_data_file.setContentsMargins(0, 0, 0, 6) self.lay_main.addItem(self.lay_data_file) self.lbl_data_file_name = QLabel(Form) self.lay_data_file.addWidget(self.lbl_data_file_name) self.lbl_data_file_badge = QLabel(Form) self.lay_data_file.addWidget(self.lbl_data_file_badge) self.lbl_data_file_name.setTextInteractionFlags( QtCore.Qt.LinksAccessibleByMouse | QtCore.Qt.TextSelectableByMouse) self.lbl_data_file_badge.setTextInteractionFlags( QtCore.Qt.LinksAccessibleByMouse | QtCore.Qt.TextSelectableByMouse) self.lay_data_file.addStretch() # actions/options area: self.lay_actions = QHBoxLayout() self.lay_actions.setSpacing(6) self.lay_actions.setContentsMargins(0, 0, 0, 0) self.lay_main.addItem(self.lay_actions) self.btn_add_recipient = QPushButton(Form) self.btn_add_recipient.clicked.connect( partial(self.add_dest_address, 1)) self.btn_add_recipient.setAutoDefault(False) self.btn_add_recipient.setText("Add recipient") self.lay_actions.addWidget(self.btn_add_recipient) # self.btn_actions = QPushButton(Form) self.btn_actions.clicked.connect(partial(self.add_dest_address, 1)) self.btn_actions.setAutoDefault(False) self.btn_actions.setText("Actions") self.lay_actions.addWidget(self.btn_actions) # context menu for the 'Actions' button self.mnu_actions = QMenu() self.btn_actions.setMenu(self.mnu_actions) a = self.mnu_actions.addAction("Load from file...") a.triggered.connect(self.on_read_from_file_clicked) self.mnu_recent_files = self.mnu_actions.addMenu('Recent files') self.mnu_recent_files.setVisible(False) a = self.mnu_actions.addAction("Save to encrypted file...") a.triggered.connect(partial(self.save_to_file, True)) a = self.mnu_actions.addAction("Save to plain CSV file...") a.triggered.connect(partial(self.save_to_file, False)) a = self.mnu_actions.addAction("Clear recipients") a.triggered.connect(self.clear_outputs) self.lbl_output_unit = QLabel(Form) self.lbl_output_unit.setText('Values as') self.lay_actions.addWidget(self.lbl_output_unit) self.cbo_output_unit = QComboBox(Form) self.cbo_output_unit.addItems(['amount', 'percentage']) self.cbo_output_unit.setCurrentIndex(0) self.cbo_output_unit.currentIndexChanged.connect( self.on_cbo_output_unit_change) self.lay_actions.addWidget(self.cbo_output_unit) self.lay_actions.addStretch(0) # scroll area for send to (destination) addresses self.scroll_area = QtWidgets.QScrollArea() self.scroll_area.setWidgetResizable(True) self.scroll_area.setMinimumHeight(30) self.scroll_area.setSizePolicy( QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.MinimumExpanding)) self.scroll_area.setFrameShape(QtWidgets.QFrame.NoFrame) self.lay_main.addWidget(self.scroll_area) self.scroll_area_widget = QtWidgets.QWidget() self.scroll_area_widget.setSizePolicy( QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.MinimumExpanding)) self.lay_scroll_area = QtWidgets.QVBoxLayout() self.lay_scroll_area.setContentsMargins(0, 0, 0, 0) self.lay_scroll_area.setSpacing(0) self.scroll_area_widget.setLayout(self.lay_scroll_area) self.scroll_area.setWidget(self.scroll_area_widget) # grid layout for destination addresses and their corresponding controls: self.lay_addresses = QtWidgets.QGridLayout() self.lay_addresses.setSpacing(3) self.lay_addresses.setContentsMargins(0, 0, 0, 0) self.lay_scroll_area.addLayout(self.lay_addresses) self.lay_scroll_area.addStretch(0) # controls for the 'change' address/amount (it's placed in the last row of the addresses grid layout): self.lbl_change_address = QLabel(self.scroll_area_widget) self.lbl_change_address.setText('Change address') self.lbl_change_address.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignTrailing | QtCore.Qt.AlignVCenter) self.lay_addresses.addWidget(self.lbl_change_address, 0, 0) # the 'change' address combobox: self.cbo_change_address = QtWidgets.QComboBox(self.scroll_area_widget) width = self.cbo_change_address.fontMetrics().width( 'XvqNXF23dRBksxjW3VQGrBtJw7vkhWhenQ') self.address_widget_width = width + 40 # combobox width on macos needs to be tweaked: self.cbo_change_address.setFixedWidth( self.address_widget_width + {'darwin': 5}.get(sys.platform, 0)) self.lay_addresses.addWidget(self.cbo_change_address, 0, 1) self.lbl_change_amount = QLabel(self.scroll_area_widget) self.set_change_value_label() self.lay_addresses.addWidget(self.lbl_change_amount, 0, 2) # read only editbox for the amount of the change: self.edt_change_amount = QLineEdit(self.scroll_area_widget) self.edt_change_amount.setFixedWidth(100) self.edt_change_amount.setReadOnly(True) self.edt_change_amount.setStyleSheet('background-color:lightgray') self.lay_addresses.addWidget(self.edt_change_amount, 0, 3) # label dedicated to the second-unit value (e.g percentage if the main unit is set to (Dash) amount value) self.lbl_second_unit = QLabel(self.scroll_area_widget) self.lay_addresses.addWidget(self.lbl_second_unit, 0, 4) # spacer spacer = QtWidgets.QSpacerItem(20, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.lay_addresses.addItem(spacer, 0, 5) # the last row of the grid layout is dedicated to 'fee' controls self.lbl_fee = QLabel(self.scroll_area_widget) self.lbl_fee.setText('Fee [Dash]') self.lbl_fee.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignTrailing | QtCore.Qt.AlignVCenter) self.lay_addresses.addWidget(self.lbl_fee, 1, 0) # the fee value editbox with the 'use default' button: self.lay_fee_value = QHBoxLayout() self.lay_fee_value.setContentsMargins(0, 0, 0, 0) self.lay_fee_value.setSpacing(0) self.lay_addresses.addItem(self.lay_fee_value, 1, 1) self.edt_fee_value = QLineEdit(self.scroll_area_widget) self.edt_fee_value.setFixedWidth(100) self.edt_fee_value.textChanged.connect(self.on_edt_fee_value_changed) self.lay_fee_value.addWidget(self.edt_fee_value) self.btn_get_default_fee = QToolButton(self.scroll_area_widget) self.btn_get_default_fee.setText('\u2b06') self.btn_get_default_fee.setFixedSize( 14, self.edt_fee_value.sizeHint().height()) self.btn_get_default_fee.setToolTip('Use default fee') self.btn_get_default_fee.clicked.connect( self.on_btn_get_default_fee_clicked) self.lay_fee_value.addWidget(self.btn_get_default_fee) self.lay_fee_value.addStretch(0) # below the addresses grid place a label dedicated do display messages self.lbl_message = QLabel(Form) self.lbl_message.setTextInteractionFlags( QtCore.Qt.LinksAccessibleByMouse | QtCore.Qt.TextSelectableByMouse) self.lbl_message.setVisible(False) self.lay_main.addWidget(self.lbl_message) # add one 'send to' address row (in most cases it will bu sufficient) self.add_dest_address(1) # load last used file names from cache mru = app_cache.get_value(CACHE_ITEM_DATA_FILE_MRU_LIST, default_value=[], type=list) if isinstance(mru, list): for file_name in mru: if os.path.exists(file_name): self.recent_data_files.append(file_name) self.update_mru_menu_items() self.retranslateUi(Form)