def run_command(self, cmd: str): log.info( 'run_command', program=self.program, args=self.args, cmd=cmd ) self.output.append(f'> {cmd}\n') self.input.clear() process = QProcess() process.setProgram(self.program) process.setCurrentReadChannel(0) # noinspection PyUnresolvedReferences process.readyReadStandardError.connect( lambda: self.handle_error(process) ) # noinspection PyUnresolvedReferences process.readyReadStandardOutput.connect( lambda: self.handle_output(process) ) connect_args = list(self.args) args = cmd.split(' ') if args[0] == self.program.split('/')[-1]: args.pop(0) process.setArguments(connect_args + args) process.start()
class Installer: def __init__(self, output_widget: QTextEdit = None): # create install process self._output_widget = None self.process = QProcess() self.process.setProgram(sys.executable) self.process.setProcessChannelMode(QProcess.MergedChannels) self.process.readyReadStandardOutput.connect(self._on_stdout_ready) # setup process path env = QProcessEnvironment() combined_paths = os.pathsep.join( [site.getsitepackages()[0], env.systemEnvironment().value("PYTHONPATH")] ) env.insert("PATH", QProcessEnvironment.systemEnvironment().value("PATH")) env.insert("PYTHONPATH", combined_paths) self.process.setProcessEnvironment(env) self.set_output_widget(output_widget) def set_output_widget(self, output_widget: QTextEdit): if output_widget: self._output_widget = output_widget self.process.setParent(output_widget) def _on_stdout_ready(self): if self._output_widget: text = self.process.readAllStandardOutput().data().decode() self._output_widget.append(text) def install(self, pkg_list): cmd = ["-m", "pip", "install", "--upgrade"] self.process.setArguments(cmd + pkg_list) if self._output_widget: self._output_widget.clear() self.process.start()
class MotivoAccess: motivoDirectory = MOTIVO_DIR def __init__(self): self.motivoProcess = QProcess() self.buildProcess = QProcess() self.mergeProcess = QProcess() self.sampleProcess = QProcess() self.graphProcess = QProcess() # TODO check motivo directory and produce an error message if needed self.motivoProcess.setWorkingDirectory(MotivoAccess.motivoDirectory + '/scripts/') self.motivoProcess.setProgram('motivo.sh') # Qproces emit readReady signal when data is ready to be read # Connect the handler for read the data self.buildProcess.setWorkingDirectory(MotivoAccess.motivoDirectory + '/build/bin/') self.buildProcess.setProgram('motivo-build') self.mergeProcess.setWorkingDirectory(MotivoAccess.motivoDirectory + '/build/bin/') self.mergeProcess.setProgram('motivo-merge') self.sampleProcess.setWorkingDirectory(MotivoAccess.motivoDirectory + '/build/bin/') self.sampleProcess.setProgram('motivo-sample') self.graphProcess.setWorkingDirectory(MotivoAccess.motivoDirectory + '/build/bin/') self.graphProcess.setProgram('motivo-graph') def runMotivo(self, motivoArguments): self.motivoProcess.setArguments(motivoArguments) self.motivoProcess.start() def runBuild(self, buildArguments): self.buildProcess.setArguments(buildArguments) self.buildProcess.start() def runMerge(self, mergeArguments): self.mergeProcess.setArguments(mergeArguments) self.mergeProcess.start() def runSample(self, sampleArguments): self.sampleProcess.setArguments(sampleArguments) self.sampleProcess.start() def runConvertTxtToBinary(self, convertTxtToBinaryArguments): self.graphProcess.setArguments(convertTxtToBinaryArguments) self.graphProcess.start()
def install(self, path): """Installs the prgram using wine""" print("Installing: " + path) uri = urllib.parse.urlparse(path) qprocess = QProcess() qprocess.finished.connect(self.onFinished) qprocess.errorOccurred.connect(self.onErrorOccurred) qprocess.setProgram("wine") qprocess.setArguments(["".join(uri[1:])]) # qprocess.start() # This Crashes qprocess.startDetached() # This does not supports signals
class Form(QPlainTextEdit): processFinished = Signal() def __init__(self, fileName, parent=None): super(Form, self).__init__(parent) self._fileName = fileName self.setStyleSheet("font-family: Source Code Pro; font-size: 16px; ") self._process = QProcess() self._process.readyReadStandardOutput.connect(self._processStdOut) self._process.readyReadStandardError.connect(self._processStdErr) self._process.finished.connect(self._processFinished) def run(self, args): self._append("> " + shlex.join(args.args) + "\n") self._lastPos = 0 self._process.setWorkingDirectory(args.dir) self._process.setProgram(args.args[0]) self._process.setArguments(args.args[1:]) self._process.start() @Slot() def putTextToFile(self): open(self._fileName, "w").write(self.toPlainText()) def keyPressEvent(self, event): k = event.key() if not self.textCursor().position() < self._lastPos: super().keyPressEvent(event) if k == Qt.Key_Return or k == Qt.Key_Enter: self._process.write( bytes(self.toPlainText()[self._lastPos:], "utf-8")) self._lastPos = self.textCursor().position() def _processStdOut(self): self._append(str(self._process.readAllStandardOutput(), "utf-8")) def _processStdErr(self): self._append(str(self._process.readAllStandardError(), "utf-8")) def _append(self, output): self.moveCursor(QTextCursor.End) self.insertPlainText(output) self.moveCursor(QTextCursor.End) self._lastPos = self.textCursor().position() def _processFinished(self, exitCode): self._append("\nProcess Finished with exit code: " + str(exitCode) + "\n") self.processFinished.emit()
def run_command(self, command): try: if self.cli is None or self.cli_args is None: self.output_area.append( 'Node starting up, please try again later...') return False self.output_area.append(f'> {command}\n') process = QProcess() process.setProgram(self.cli) process.setCurrentReadChannel(0) # noinspection PyUnresolvedReferences process.readyReadStandardError.connect( lambda: self.handle_cli_error_output(process)) # noinspection PyUnresolvedReferences process.readyReadStandardOutput.connect( lambda: self.handle_cli_output(process)) args = command.split(' ') if args[0] == self.cli.split('/')[-1]: args.pop(0) process.setArguments(self.cli_args + args) process.start() log.info('run_command', program=self.cli, args=self.cli_args, cmd=command) return True except Exception: self.output_area.append( 'Node starting up, please try again later...') return False
class Litecoin(object): file: ConfigurationFile hard_drives: HardDrives process: QProcess software: LitecoinSoftware zmq_block_port: int zmq_tx_port: int def __init__(self, configuration_file_path: str): self.hard_drives = HardDrives() self.software = LitecoinSoftware() self.file = ConfigurationFile(configuration_file_path) self.running = False self.process = None if self.file['datadir'] is None: self.autoconfigure_datadir() if 'litecoin.conf' in os.listdir(self.file['datadir']): actual_conf_file = os.path.join(self.file['datadir'], 'litecoin.conf') log.info( 'datadir_redirect', configuration_file_path=configuration_file_path, actual_conf_file=actual_conf_file ) self.file = ConfigurationFile(actual_conf_file) if self.file['datadir'] is None: 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_litecoin=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}' # 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) self.process = QProcess() self.process.setProgram(self.software.litecoind) self.process.setCurrentReadChannel(0) self.process.setArguments(self.args) self.process.start() @property def network(self): if self.file['testnet']: return TESTNET return MAINNET def get_wallet_paths(self): exclude_files = { 'addr.dat', 'banlist.dat', 'fee_estimates.dat', 'mempool.dat', 'peers.dat' } candidate_paths = [] if self.file['testnet']: datadir = os.path.join(self.file['datadir'], 'testnet4') wallet_dir = self.file['test.walletdir'] wallets = self.file['test.wallet'] else: 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): if self.file['testnet']: custom_port = self.file['test.port'] else: custom_port = self.file['main.port'] if custom_port is not None: return custom_port if self.file['testnet']: return LITECOIN_TESTNET_PEER_PORT return LITECOIN_MAINNET_PEER_PORT @property def rpc_port(self): if self.file['testnet']: custom_port = self.file['test.rpcport'] else: custom_port = self.file['main.rpcport'] if custom_port is not None: return custom_port if self.file['testnet']: return LITECOIN_TESTNET_RPC_PORT return LITECOIN_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_litecoin=True) if should_prune: if self.file['testnet']: prune = TESTNET_PRUNE else: prune = MAINNET_PRUNE self.file['prune'] = prune else: self.file['prune'] = 0 self.file['txindex'] = not should_prune def autoconfigure_datadir(self): default_datadir = LITECOIN_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_litecoin=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, 'Litecoin') 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 ) @property def args(self) -> List[str]: return [f'-conf={self.file.path}'] @property def litecoin_cli(self) -> str: command = [ f'"{self.software.litecoin_cli}"', f'-conf="{self.file.path}"', ] return ' '.join(command) def config_file_changed(self): # Refresh config file self.file.file_watcher.blockSignals(True) self.file.populate_cache() self.file.file_watcher.blockSignals(False) self.zmq_block_port = int(self.file['zmqpubrawblock'].split(':')[-1]) 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 if self.file['testnet']: # Only check testnet fields if currently running testnet testnet_fields = [ 'test.rpcport', 'test.port', 'test.wallet', 'test.walletdir' ] for field in testnet_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 Lnd(object): litecoin: Litecoin file: ConfigurationFile software: LndSoftware process: QProcess def __init__(self, configuration_file_path: str, litecoin: Litecoin): self.running = False self.is_unlocked = False self.litecoin = litecoin self.file = ConfigurationFile(configuration_file_path) self.software = LndSoftware() 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['litecoin.active'] = True self.file['litecoin.node'] = 'litecoind' self.file['litecoind.rpchost'] = f'127.0.0.1:{self.litecoin.rpc_port}' self.file['litecoind.rpcuser'] = self.litecoin.file['rpcuser'] self.file['litecoind.rpcpass'] = self.litecoin.file['rpcpassword'] self.file['litecoind.zmqpubrawblock'] = self.litecoin.file[ 'zmqpubrawblock'] self.file['litecoind.zmqpubrawtx'] = self.litecoin.file['zmqpubrawtx'] if self.file['restlisten'] is None: if self.litecoin.file['testnet']: self.rest_port = get_port(LND_DEFAULT_REST_PORT + 1) else: 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']: if self.litecoin.file['testnet']: self.grpc_port = get_port(LND_DEFAULT_GRPC_PORT + 1) else: 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.macaroon_path = os.path.join( self.lnddir, 'data', 'chain', 'litecoin', str(self.litecoin.network) ) self.config_snapshot = self.file.snapshot.copy() self.file.file_watcher.fileChanged.connect(self.config_file_changed) self.litecoin.file.file_watcher.fileChanged.connect(self.litecoin_config_file_changed) self.process = QProcess() self.process.setProgram(self.software.lnd) self.process.setCurrentReadChannel(0) self.process.setArguments(self.args) self.process.start() @property def args(self): if IS_WINDOWS: arg_list = [ f'--configfile={self.file.path}', ] else: arg_list = [ f'--configfile="{self.file.path}"', ] if self.litecoin.file['testnet']: arg_list += [ '--litecoin.testnet' ] else: arg_list += [ '--litecoin.mainnet' ] return arg_list @property def node_port(self) -> str: if self.file['listen'] is None: if self.litecoin.file['testnet']: port = get_port(LND_DEFAULT_PEER_PORT + 1) else: 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 = self.file['listen'].split(':')[-1] else: port = self.file['listen'][0].split(':')[-1] return port 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 @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 def lncli_arguments(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.litecoin.file['testnet']: args.append(f'--network={self.litecoin.network}') 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 @property def lncli(self) -> str: base_command = [ f'"{self.software.lncli}"', ] base_command += self.lncli_arguments() return ' '.join(base_command) @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}' def config_file_changed(self): # Refresh config file self.file.file_watcher.blockSignals(True) self.file.populate_cache() self.file.file_watcher.blockSignals(False) self.rest_port = int(self.file['restlisten'].split(':')[-1]) 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 litecoin_config_file_changed(self): # Refresh config file self.file.file_watcher.blockSignals(True) self.file.populate_cache() self.file.file_watcher.blockSignals(False) self.file['litecoind.rpchost'] = f'127.0.0.1:{self.litecoin.rpc_port}' self.file['litecoind.rpcuser'] = self.litecoin.file['rpcuser'] self.file['litecoind.rpcpass'] = self.litecoin.file['rpcpassword'] self.file['litecoind.zmqpubrawblock'] = self.litecoin.file['zmqpubrawblock'] self.file['litecoind.zmqpubrawtx'] = self.litecoin.file['zmqpubrawtx'] @property def restart_required(self): if self.running: # Did litecoin details change if self.litecoin.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
class CliWidget(QDialog): def __init__(self, title: str, program: str, args: List[str], commands: List[str]): super().__init__() self.setWindowTitle(title) self.program = program self.args = args self.layout = QGridLayout() self.output = QTextEdit() self.output.acceptRichText = True self.input = QLineEdit() self.completer = QCompleter(commands, self) self.completer.setCaseSensitivity(Qt.CaseInsensitive) self.input.setCompleter(self.completer) self.input.setFocus() self.process = QProcess() self.process.setProgram(self.program) self.process.setCurrentReadChannel(0) # noinspection PyUnresolvedReferences self.process.readyReadStandardError.connect(self.handle_error) # noinspection PyUnresolvedReferences self.process.readyReadStandardOutput.connect(self.handle_output) self.layout.addWidget(self.output) self.layout.addWidget(self.input) self.setLayout(self.layout) self.connect(self.input, SIGNAL("returnPressed(void)"), self.execute_user_command) self.connect(self.completer, SIGNAL("activated(const QString&)"), self.input.clear, Qt.QueuedConnection) def execute_user_command(self): cmd = str(self.input.text()) self.run_command(cmd) def run_command(self, cmd: str): log.info('run_command', program=self.program, args=self.args, cmd=cmd) self.output.append(f'> {cmd}\n') self.input.clear() self.process.kill() args = list(self.args) args.append(cmd) self.process.setArguments(args) self.process.start() def handle_error(self): output: QByteArray = self.process.readAllStandardError() message = output.data().decode('utf-8').strip() self.output.append(message) def handle_output(self): output: QByteArray = self.process.readAllStandardOutput() message = output.data().decode('utf-8').strip() if message.startswith('{') or message.startswith('['): formatter = HtmlFormatter() formatter.noclasses = True formatter.linenos = False formatter.nobackground = True message = highlight(message, JsonLexer(), formatter) self.output.insertHtml(message) else: self.output.append(message) # This is just for generating the command lists in constants # commands = None # if '== Blockchain ==' in message: # commands = self.parse_bitcoin_cli_commands(message) # elif 'lncli [global options] command [command options]' in message: # commands = self.parse_lncli_commands(message) # if commands is not None: # log.debug('commands', commands=commands) max_scroll = self.output.verticalScrollBar().maximum() self.output.verticalScrollBar().setValue(max_scroll) def parse_bitcoin_cli_commands(self, message: str): log.debug('parse_bitcoin_cli_commands') commands = [] for line in message.split(sep='\n'): line = line.strip() if not line or line.startswith('=='): continue command = line.split()[0] command = command.strip() commands.append(command) return commands def parse_lncli_commands(self, message: str): log.debug('parse_lncli_commands') at_commands = False commands = [] for line in message.split(sep='\n'): line = line.strip() if not at_commands: if 'COMMANDS:' in line: at_commands = True log.debug('commands line', line=line) continue elif 'GLOBAL OPTIONS' in line: return commands elif line.endswith(':') or not line: continue command = line.split()[0] command = command.strip().replace(',', '') commands.append(command) return commands def show(self): self.showMaximized() self.input.setFocus() self.run_command('help')