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()
def save(self, url): self._saving = True self.savingChanged.emit() self._savingProgress = 0 self.savingProgressChanged.emit() p = QProcess(self) p.setProcessChannelMode(QProcess.ForwardedErrorChannel) stdout_buffer = b"" def ready_read_stdout(): nonlocal stdout_buffer stdout_buffer += p.readAllStandardOutput().data() *messages, stdout_buffer = stdout_buffer.split(b"\n") for message in messages: progress = json.loads(messages[-1].decode()) self._savingProgress = progress["fraction"] self.savingProgressChanged.emit() p.readyReadStandardOutput.connect(ready_read_stdout) def finished(status): self._saving = False self.savingChanged.emit() if status != 0: self.savingError.emit() p.finished.connect(finished) args = ["-c", "from djpdf.scans2pdf import main; main()", os.path.abspath(url.toLocalFile())] if self._verbose: args.append("--verbose") p.start(sys.executable, args) p.write(json.dumps([p._data for p in self._pages]).encode()) p.closeWriteChannel()
def save(self, url): self._saving = True self.savingChanged.emit() self._savingProgress = 0 self.savingProgressChanged.emit() p = QProcess(self) p.setProcessChannelMode(QProcess.ForwardedErrorChannel) stdout_buffer = b"" def ready_read_stdout(): nonlocal stdout_buffer stdout_buffer += p.readAllStandardOutput().data() *messages, stdout_buffer = stdout_buffer.split(b"\n") for message in messages: progress = json.loads(messages[-1].decode()) self._savingProgress = progress["fraction"] self.savingProgressChanged.emit() p.readyReadStandardOutput.connect(ready_read_stdout) p.finished.connect(self._process_finished) args = ["-c", "from djpdf.scans2pdf import main; main()", os.path.abspath(url.toLocalFile())] if self._verbose: args.append("--verbose") p.start(sys.executable, args) self._process = p p.write(json.dumps([p._data for p in self._pages]).encode()) p.closeWriteChannel()
class SerialControl(QObject): sig_keyseq_pressed = Signal(str) sig_CPU_comout = Signal(int) sig_CPU_comin = Signal(str) sig_port_change = Signal(str) sig_button_pressed = Signal(int) sig_terminal_open = Signal(bool) sig_firmware_update = Signal(str) def __init__(self, cpu, monitor_frame, terminal_frame, usb_frame, statusbar, config, sig_update, config_file_path): QObject.__init__(self) self.cpu = cpu self.monitor = monitor_frame self.terminal = terminal_frame self.statusbar = statusbar self.config = config self.ser_port = None self.sig_update = sig_update self.fd_thread = None self.fwth = None self.monitor_frame = monitor_frame self.usb_frame = usb_frame self.config_file_path = config_file_path # Connect signal self.sig_keyseq_pressed.connect(self.on_key_pressed) self.sig_CPU_comout.connect(self.on_comout) self.sig_CPU_comin.connect(self.on_comin) self.sig_port_change.connect(self.on_port_change) self.sig_button_pressed.connect(self.on_button_dispatch) self.sig_terminal_open.connect(self.on_terminal_open) self.sig_firmware_update.connect(self.on_firmware_update) self.monitor_frame.sig_keyseq_pressed = self.sig_keyseq_pressed self.monitor_frame.sig_button_pressed = self.sig_button_pressed self.cpu.sig_CPU_comout = self.sig_CPU_comout self.cpu.sig_CPU_comin = self.sig_CPU_comin self.usb_frame.usb_combo.sig_port_change = self.sig_port_change self.usb_frame.sig_button_pressed = self.sig_button_pressed self.usb_frame.sig_firmware_update = self.sig_firmware_update self.terminal.sig_terminal_open = self.sig_terminal_open # Disable fw flash button if udr2 binary is nor present self.udr2 = f"cli/udr2-{sys.platform}" if sys.platform != "win32" else "cli\\udr2-win32.exe" if not os.path.isfile(self.udr2): self.usb_frame.firmware_btn.setEnabled(False) self.init_serial() def init_serial(self, do_refresh=True): is_thread = InitSerialThread(self) is_thread.update_combo = self.usb_frame.usb_combo.set_ports is_thread.do_refresh = do_refresh is_thread.start() def init_OK(self): self.statusbar.sig_temp_message.emit("Serial port Initialized") # # Signal Handling # @Slot(str) def on_key_pressed(self, key): if self.cpu.rx is None and len(key) == 1: self.cpu.rx = key self.monitor_frame.serial_in.setText(key) @Slot(str) def on_comin(self, char): """Char is handled by CPU, we free the slot""" self.monitor_frame.serial_in.setText(" ") @Slot(int) def on_comout(self, byte): """Append the char to the console""" self.monitor_frame.append_serial_out(byte) @Slot(str) def on_port_change(self, port): self.config.set("serial", "port", port) if self.ser_port: self.ser_port.port = port with open(self.config_file_path, 'w') as configfile: self.config.write(configfile) self.init_serial(False) @Slot(int) def on_button_dispatch(self, btn_nbr): if btn_nbr == 0: self.to_digirule() elif btn_nbr == 1: self.from_digirule() elif btn_nbr == 2: self.init_serial() elif btn_nbr == 3: self.on_clear_button() @Slot(bool) def on_terminal_open(self, is_open): if is_open: # Terminal window is open : create the terminal serial thread self.statusbar.sig_temp_message.emit("open terminal thread") self.terminal.serth = SerialThread(self) # Start serial thread self.terminal.serth.start() else: # Terminal window is closed : quit the terminal serial thread if self.terminal.serth: self.terminal.serth.running = False sleep(0.5) self.terminal.serth = None def on_clear_button(self): self.monitor_frame.clear() # Clear the serial in/out content self.cpu.rx = None self.cpu.tx = None def to_digirule(self): if self.ser_port is None: self.statusbar.sig_temp_message.emit( "Error : No serial port configured") else: dump = ram2hex(self.cpu.ram) self.statusbar.sig_temp_message.emit("Dumpimg memory on port " + self.ser_port.port) try: self.ser_port.open() except serial.serialutil.SerialException as ex: self.statusbar.sig_temp_message.emit(ex.strerror) else: for line in dump.splitlines(): self.ser_port.write(line.encode("utf-8")) sleep(0.1) sleep(2) self.ser_port.close() self.statusbar.sig_temp_message.emit("Memory sent") def from_digirule(self): """Launch receive sequence in background""" if self.ser_port: self.statusbar.sig_temp_message.emit( "Waiting to receive Digirule on " + self.ser_port.port) self.fd_thread = FromDigiruleThread(self) self.fd_thread.start() else: self.init_serial() @Slot(str) def on_firmware_update(self, filepath): if self.ser_port: self.proc = QProcess(self) self.proc.readyReadStandardOutput.connect(self.stdoutReady) self.proc.readyReadStandardError.connect(self.stderrReady) if sys.platform == "win32": command = f'{self.udr2} --program {self.ser_port.port} < {filepath}' self.usb_frame.out.write( "Firmware update started, please wait ") # displays running dots on windows to pretend it is not stalled self.bullshitTimer = QTimer() self.bullshitTimer.timeout.connect(self.stdoutBullshit) self.bullshitTimer.start(1000) self.proc.setProcessChannelMode(QProcess.MergedChannels) self.proc.start('cmd.exe', ['/c', command]) else: command = f'{self.udr2} --program {self.ser_port.port} < "{filepath}"' self.bullshitTimer = None self.proc.start('bash', ['-c', command]) # print(command) def stdoutBullshit(self): self.usb_frame.out.write(".") def stdoutReady(self): if self.bullshitTimer: self.usb_frame.out.write("\n") self.bullshitTimer.stop() text = str(self.proc.readAllStandardOutput()) self.usb_frame.out.write(eval(text).decode('iso8859-1')) def stderrReady(self): text = str(self.proc.readAllStandardError()) self.usb_frame.out.write(eval(text).decode('iso8859-1'))
class QKonsol(QPlainTextEdit): userTextEntry = "" commandList = ["cd " + QDir.homePath()] length = 0 history = -1 def __init__(self, parent=None): super().__init__() self.setParent(parent) self.setWindowTitle(self.tr("Terminal")) self.setCursorWidth(7) self.setContextMenuPolicy(Qt.NoContextMenu) font = self.font() font.setFamily("Consolas") font.setPointSize(10) self.setFont(font) self.setUndoRedoEnabled(False) palette = QPalette() palette.setColor(QPalette.Base, Qt.black) palette.setColor(QPalette.Text, Qt.white) palette.setColor(QPalette.Highlight, Qt.white) palette.setColor(QPalette.HighlightedText, Qt.black) self.setFrameShape(QFrame.NoFrame) self.setPalette(palette) self.resize(720, 480) self.process = QProcess(self) self.process.setProcessChannelMode(QProcess.MergedChannels) self.process.setReadChannel(QProcess.StandardOutput) self.process.readyReadStandardOutput.connect(self.readStandartOutput) self.process.readyReadStandardError.connect( lambda: print(self.readAllStandartError())) self.cursorPositionChanged.connect(self.cursorPosition) self.textChanged.connect(self.whatText) if sys.platform == "win32": self.process.start("cmd.exe", [], mode=QProcess.ReadWrite) else: self.process.start( "bash", ["-i"], mode=QProcess.ReadWrite) # bash -i interactive mode def readStandartOutput(self): if sys.platform == "win32": st = self.process.readAllStandardOutput().data().decode( str(ctypes.cdll.kernel32.GetConsoleOutputCP())) else: st = self.process.readAllStandardOutput().data().decode("utf-8") # print(repr(st), self.commandList) if not st.startswith(self.commandList[-1]): self.appendPlainText(st) def __line_end(self): if sys.platform == "win32": return "\r\n" elif sys.platform == "linux": return "\n" elif sys.platform == "darwin": return "\r" def keyPressEvent(self, event: QKeyEvent): if event.key() in (Qt.Key_Enter, Qt.Key_Return): if self.commandList[ -1] != self.userTextEntry and self.userTextEntry != "": self.commandList.append(self.userTextEntry) self.length = len(self.userTextEntry + self.__line_end()) self.process.writeData(self.userTextEntry + self.__line_end(), self.length) self.userTextEntry = "" elif event.key() == Qt.Key_Backspace: if self.userTextEntry == "": return else: self.userTextEntry = self.userTextEntry[:-1] super().keyPressEvent(event) elif event.key() == Qt.Key_Up: if -len(self.commandList) < self.history: self.history -= 1 print(self.commandList[self.history]) return elif event.key() == Qt.Key_Down: if self.history < -1: self.history += 1 print(self.commandList[self.history]) return elif event.key() == Qt.Key_Delete: return elif event.modifiers() == Qt.ControlModifier: super().keyPressEvent(event) else: super().keyPressEvent(event) self.userTextEntry += event.text() def cursorPosition(self): pass # print(self.textCursor().position()) def whatText(self): pass # print(self.blockCount()) def insertFromMimeData(self, source): super().insertFromMimeData(source) self.userTextEntry += source.text() def mouseReleaseEvent(self, event): super().mousePressEvent(event) cur = self.textCursor() if event.button() == Qt.LeftButton: cur.movePosition(QTextCursor.End, QTextCursor.MoveAnchor, 1) self.setTextCursor(cur) def closeEvent(self, event): self.process.close()
class Bitcoin(object): file: ConfigurationFile hard_drives: HardDrives process: QProcess software: BitcoinSoftware zmq_block_port: int zmq_tx_port: int def __init__(self, configuration_file_path: str): self.hard_drives = HardDrives() self.software = BitcoinSoftware() self.file = ConfigurationFile(configuration_file_path) self.running = False self.process = None 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 configuration_file_path != actual_conf_file: 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 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}' # 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.bitcoind) self.process.setProcessChannelMode(QProcess.MergedChannels) self.process.setArguments(self.args) @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'], 'testnet3') 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 BITCOIN_TESTNET_PEER_PORT return BITCOIN_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 BITCOIN_TESTNET_RPC_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: 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 = 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 ) @property def args(self) -> List[str]: return [f'-conf={self.file.path}'] @property def bitcoin_cli(self) -> str: command = [ f'"{self.software.bitcoin_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) 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 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