Beispiel #1
0
    def __init__(self, *args, **kwargs):
        super(AppWindow, self).__init__(*args, **kwargs)
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        # translations..?!
        self._translate = QCoreApplication.translate

        self.check_config()

        # initialize attributes
        self.invoice_to_check = None
        self.invoice_to_check_flag = None

        self.uptime = 0

        self.status_lnd_due = 0
        self.status_lnd_interval = STATUS_INTERVAL_LND
        self.status_lnd_pid_ok = False
        self.status_lnd_listen_ok = False
        self.status_lnd_unlocked = False
        self.status_lnd_synced_to_chain = False
        self.status_lnd_synced_to_graph = False

        self.status_lnd_channel_due = 0
        self.status_lnd_channel_interval = STATUS_INTERVAL_LND_CHANNELS
        self.status_lnd_channel_total_active = 0
        self.status_lnd_channel_total_remote_balance = 0

        # initial updates
        self.update_uptime()
        if self.cfg_valid:
            self.update_status_lnd()
            self.update_status_lnd_channels()

        # initial update of Main Window Title Bar
        self.update_title_bar()

        # Align Main Window Top Left
        self.move(0, 0)

        # set as maximized (unless on Windows dev host)
        if IS_WIN32_ENV:
            log.info("not maximizing window on win32")
        else:
            self.setWindowState(Qt.WindowMaximized)

        # Bindings: buttons
        self.ui.pushButton_1.clicked.connect(self.on_button_1_clicked)
        self.ui.pushButton_2.clicked.connect(self.on_button_2_clicked)
        self.ui.pushButton_3.clicked.connect(self.on_button_3_clicked)
        self.ui.pushButton_4.clicked.connect(self.on_button_4_clicked)

        # disable button 1 for now
        self.ui.pushButton_1.setEnabled(False)

        # connect error dismiss button and hide for start
        self.ui.buttonBox_close.button(QDialogButtonBox.Close).setText("Ok")
        self.ui.buttonBox_close.button(QDialogButtonBox.Close).clicked.connect(
            self.hide_error)
        self.hide_error()

        # Show QR Code Dialog Windows
        self.w_qr_code = QDialog(flags=(Qt.Dialog | Qt.FramelessWindowHint))
        self.ui_qr_code = Ui_DialogShowQrCode()
        self.ui_qr_code.setupUi(self.w_qr_code)
        self.w_qr_code.move(0, 0)

        # SPINNER for CR Code Dialog Window
        self.ui_qr_code.spinner = WaitingSpinner(self.w_qr_code)

        self.beat_thread = BeatThread()
        self.beat_thread.signal.connect(self.process_beat)
        self.beat_thread.start()

        self.generate_qr_code_thread = GenerateQrCodeThread()
        self.generate_qr_code_thread.signal.connect(
            self.generate_qr_code_finished)

        self.file_watcher = FileWatcherThread(
            dir_names=[
                os.path.dirname(LND_CONF),
                os.path.dirname(RB_CONF),
                os.path.dirname(RB_INFO)
            ],
            file_names=[
                os.path.basename(LND_CONF),
                os.path.basename(RB_CONF),
                os.path.basename(RB_INFO)
            ],
        )
        self.file_watcher.signal.connect(self.update_watched_attr)
        self.file_watcher.start()

        # finally start 00infoBlitz.sh in dedicated xterm frame
        self.start_info_lcd()

        self.show()
Beispiel #2
0
class AppWindow(QMainWindow):
    def __init__(self, *args, **kwargs):
        super(AppWindow, self).__init__(*args, **kwargs)
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        # translations..?!
        self._translate = QCoreApplication.translate

        self.check_config()

        # initialize attributes
        self.invoice_to_check = None
        self.invoice_to_check_flag = None

        self.uptime = 0

        self.status_lnd_due = 0
        self.status_lnd_interval = STATUS_INTERVAL_LND
        self.status_lnd_pid_ok = False
        self.status_lnd_listen_ok = False
        self.status_lnd_unlocked = False
        self.status_lnd_synced_to_chain = False
        self.status_lnd_synced_to_graph = False

        self.status_lnd_channel_due = 0
        self.status_lnd_channel_interval = STATUS_INTERVAL_LND_CHANNELS
        self.status_lnd_channel_total_active = 0
        self.status_lnd_channel_total_remote_balance = 0

        # initial updates
        self.update_uptime()
        if self.cfg_valid:
            self.update_status_lnd()
            self.update_status_lnd_channels()

        # initial update of Main Window Title Bar
        self.update_title_bar()

        # Align Main Window Top Left
        self.move(0, 0)

        # set as maximized (unless on Windows dev host)
        if IS_WIN32_ENV:
            log.info("not maximizing window on win32")
        else:
            self.setWindowState(Qt.WindowMaximized)

        # Bindings: buttons
        self.ui.pushButton_1.clicked.connect(self.on_button_1_clicked)
        self.ui.pushButton_2.clicked.connect(self.on_button_2_clicked)
        self.ui.pushButton_3.clicked.connect(self.on_button_3_clicked)
        self.ui.pushButton_4.clicked.connect(self.on_button_4_clicked)

        # disable button 1 for now
        self.ui.pushButton_1.setEnabled(False)

        # connect error dismiss button and hide for start
        self.ui.buttonBox_close.button(QDialogButtonBox.Close).setText("Ok")
        self.ui.buttonBox_close.button(QDialogButtonBox.Close).clicked.connect(
            self.hide_error)
        self.hide_error()

        # Show QR Code Dialog Windows
        self.w_qr_code = QDialog(flags=(Qt.Dialog | Qt.FramelessWindowHint))
        self.ui_qr_code = Ui_DialogShowQrCode()
        self.ui_qr_code.setupUi(self.w_qr_code)
        self.w_qr_code.move(0, 0)

        # SPINNER for CR Code Dialog Window
        self.ui_qr_code.spinner = WaitingSpinner(self.w_qr_code)

        self.beat_thread = BeatThread()
        self.beat_thread.signal.connect(self.process_beat)
        self.beat_thread.start()

        self.generate_qr_code_thread = GenerateQrCodeThread()
        self.generate_qr_code_thread.signal.connect(
            self.generate_qr_code_finished)

        self.file_watcher = FileWatcherThread(
            dir_names=[
                os.path.dirname(LND_CONF),
                os.path.dirname(RB_CONF),
                os.path.dirname(RB_INFO)
            ],
            file_names=[
                os.path.basename(LND_CONF),
                os.path.basename(RB_CONF),
                os.path.basename(RB_INFO)
            ],
        )
        self.file_watcher.signal.connect(self.update_watched_attr)
        self.file_watcher.start()

        # finally start 00infoBlitz.sh in dedicated xterm frame
        self.start_info_lcd()

        self.show()

    def start_info_lcd(self, pause=12):
        # if system has been running for more than 180 seconds then skip pause
        if self.uptime > 180:
            pause = 0

        process = QProcess(self)
        process.setProcessChannelMode(QProcess.MergedChannels)
        # connect the stdout_item to the Process StandardOutput
        # it gets constantly update as the process emit std output
        process.readyReadStandardOutput.connect(lambda: log.info(
            str(process.readAllStandardOutput().data().decode('utf-8'))))

        process.start('xterm', [
            '-fn', 'fixed', '-into',
            str(int(self.ui.widget.winId())), '+sb', '-hold', '-e',
            'bash -c \"/home/admin/00infoLCD.sh --pause {}\"'.format(pause)
        ])

    def check_config(self):
        if IS_WIN32_ENV:
            log.info("using dummy config on win32")
            lnd_cfg_abs_path = os.path.join(os.path.dirname(__file__), "..",
                                            "data", os.path.basename(LND_CONF))
            rb_cfg_abs_path = os.path.join(os.path.dirname(__file__), "..",
                                           "data", os.path.basename(RB_CONF))
            rb_info_abs_path = os.path.join(os.path.dirname(__file__), "..",
                                            "data", os.path.basename(RB_INFO))
        else:
            lnd_cfg_abs_path = LND_CONF
            rb_cfg_abs_path = RB_CONF
            rb_info_abs_path = RB_INFO

        # read config and info files
        if not os.path.exists(lnd_cfg_abs_path):
            log.warning("file does not exist: {}".format(lnd_cfg_abs_path))

        if not os.path.exists(rb_cfg_abs_path):
            log.warning("file does not exist: {}".format(rb_cfg_abs_path))

        if not os.path.exists(rb_info_abs_path):
            log.warning("file does not exist: {}".format(rb_info_abs_path))

        log.debug("init lnd.conf")
        lnd_cfg_valid = False
        self.lnd_cfg = LndConfig(lnd_cfg_abs_path)
        try:
            self.lnd_cfg.reload()
            lnd_cfg_valid = True
        except Exception as err:
            pass

        log.debug("init raspiblitz.conf")
        rb_cfg_valid = False
        self.rb_cfg = RaspiBlitzConfig(rb_cfg_abs_path)
        try:
            self.rb_cfg.reload()
            rb_cfg_valid = True
        except Exception as err:
            pass

        log.debug("init raspiblitz.info")
        rb_info_valid = False
        self.rb_info = RaspiBlitzInfo(rb_info_abs_path)
        try:
            self.rb_info.reload()
            rb_info_valid = True
        except Exception as err:
            pass

        self.cfg_valid = lnd_cfg_valid and rb_cfg_valid and rb_info_valid
        log.debug("checked cfg_valid with result: {}".format(self.cfg_valid))

    def check_invoice(self, flag, tick=0):
        log.info("checking invoice paid (Tick: {})".format(tick))
        self.invoice_to_check_flag = flag

        if tick >= INVOICE_CHECK_TIMEOUT:
            log.debug("canceled checking invoice paid")
            flag.set()

        if IS_DEV_ENV:
            res = False
            amt_paid_sat = 123123402

            if tick == 5:
                res = True

        else:
            with ReadOnlyStub(network=self.rb_cfg.network,
                              chain=self.rb_cfg.chain) as stub_readonly:
                res, amt_paid_sat = check_invoice_paid(stub_readonly,
                                                       self.invoice_to_check)
                log.debug("result of invoice check: {}".format(res))

        if res:
            log.debug("paid!")
            self.ui_qr_code.qcode.setMargin(8)
            self.ui_qr_code.qcode.setPixmap(
                QPixmap(":/RaspiBlitz/images/Paid_Stamp.png"))

            if amt_paid_sat:
                self.ui_qr_code.status_value.setText("Paid")
                self.ui_qr_code.amt_paid_value.setText(
                    "{}".format(amt_paid_sat))
            else:
                self.ui_qr_code.status_value.setText("Paid")

            flag.set()

    def update_status_lnd(self):

        if IS_WIN32_ENV:
            return

        if self.status_lnd_due <= self.uptime:
            log.debug("updating status_lnd")

            try:
                with ReadOnlyStub(network=self.rb_cfg.network,
                                  chain=self.rb_cfg.chain) as stub_readonly:
                    pid_ok, listen_ok, unlocked, synced_to_chain, synced_to_graph = check_lnd(
                        stub_readonly)
                    self.status_lnd_pid_ok = pid_ok
                    self.status_lnd_listen_ok = listen_ok
                    self.status_lnd_unlocked = unlocked
                    self.status_lnd_synced_to_chain = synced_to_chain
                    self.status_lnd_synced_to_graph = synced_to_graph
                    # set next due time
                    self.status_lnd_due = self.uptime + self.status_lnd_interval
            except Exception as err:
                log.info("Exception on update_status_lnd")
                pass

    def update_status_lnd_channels(self):

        if IS_WIN32_ENV:
            return

        log.debug("update_status_lnd_channel due: {}".format(
            self.status_lnd_channel_due))
        if self.status_lnd_channel_due <= self.uptime:
            log.debug("updating status_lnd_channels")

            try:
                with ReadOnlyStub(network=self.rb_cfg.network,
                                  chain=self.rb_cfg.chain) as stub_readonly:
                    self.status_lnd_channel_total_active, self.status_lnd_channel_total_remote_balance = \
                        check_lnd_channels(stub_readonly)
                    # set next due time
                    self.status_lnd_channel_due = self.uptime + self.status_lnd_channel_interval
            except Exception as err:
                log.info("Exception on update_status_lnd_channels")
                pass

    def update_title_bar(self):
        log.debug("updating: Main Window Title Bar")
        self.setWindowTitle(
            self._translate(
                "MainWindow",
                "RaspiBlitz v{} - {} - {}net".format(self.rb_cfg.version,
                                                     self.rb_cfg.network,
                                                     self.rb_cfg.chain)))

    def update_uptime(self):
        if IS_WIN32_ENV:
            self.uptime += 1
        else:
            with open('/proc/uptime', 'r') as f:
                self.uptime = float(f.readline().split()[0])
            # log.info("Uptime: {}".format(self.uptime))

    def process_beat(self, _):
        self.check_config()
        self.update_uptime()
        if self.cfg_valid:
            self.update_status_lnd()
            self.update_status_lnd_channels()

    def update_watched_attr(self):
        log.debug("updating: watched attributes")
        self.lnd_cfg.reload()
        self.rb_cfg.reload()
        self.rb_info.reload()

        # add anything here that should be updated now too
        self.update_title_bar()

    def hide_error(self):
        self.ui.error_label.hide()
        self.ui.buttonBox_close.hide()

    def show_qr_code(self,
                     data,
                     screen=None,
                     memo=None,
                     status=None,
                     inv_amt=None,
                     amt_paid="N/A"):
        log.debug("show_qr_code: {}".format(data))
        # reset to logo and set text
        self.ui_qr_code.qcode.setMargin(48)
        self.ui_qr_code.qcode.setPixmap(
            QPixmap(":/RaspiBlitz/images/RaspiBlitz_Logo_Stacked.png"))

        if screen == SCREEN_NODE_URI:
            self.ui_qr_code.memo_key.show()
            self.ui_qr_code.memo_key.setText("Node URI")

            _tmp = data.split("@")
            pub = _tmp[0]
            _tmp2 = _tmp[1].split(":")
            host = _tmp2[0]
            port = _tmp2[1]

            n = 16
            pub = [(pub[i:i + n]) for i in range(0, len(pub), n)]
            host = [(host[i:i + n]) for i in range(0, len(host), n)]
            self.ui_qr_code.memo_value.show()
            self.ui_qr_code.memo_value.setText("{} \n@{} \n:{}".format(
                " ".join(pub), " ".join(host), port))

            self.ui_qr_code.status_key.hide()
            self.ui_qr_code.status_value.hide()
            self.ui_qr_code.inv_amt_key.hide()
            self.ui_qr_code.inv_amt_value.hide()
            self.ui_qr_code.amt_paid_key.hide()
            self.ui_qr_code.amt_paid_value.hide()

        if screen == SCREEN_INVOICE:
            self.ui_qr_code.memo_key.show()
            self.ui_qr_code.memo_key.setText("Invoice Memo")

            self.ui_qr_code.memo_value.show()
            self.ui_qr_code.memo_value.setText(memo)

            self.ui_qr_code.status_key.show()
            self.ui_qr_code.status_value.show()
            self.ui_qr_code.status_value.setText(status)

            self.ui_qr_code.inv_amt_key.show()
            self.ui_qr_code.inv_amt_value.show()
            self.ui_qr_code.inv_amt_value.setText("{}".format(inv_amt))

            self.ui_qr_code.amt_paid_key.show()
            self.ui_qr_code.amt_paid_value.show()
            self.ui_qr_code.amt_paid_value.setText("{}".format(amt_paid))

        # set function and start thread
        self.generate_qr_code_thread.data = data
        self.generate_qr_code_thread.start()
        self.ui_qr_code.spinner.start()

        self.w_qr_code.activateWindow()
        self.w_qr_code.show()

        rsp = self.w_qr_code.exec_()
        if rsp == QDialog.Accepted:
            log.info("QR: pressed OK - canceling invoice check")
            if self.invoice_to_check_flag:
                self.invoice_to_check_flag.set()

    def generate_qr_code_finished(self, img):
        buf = BytesIO()
        img.save(buf, "PNG")

        qt_pixmap = QPixmap()
        qt_pixmap.loadFromData(buf.getvalue(), "PNG")
        self.ui_qr_code.spinner.stop()
        self.ui_qr_code.qcode.setMargin(2)
        self.ui_qr_code.qcode.setPixmap(qt_pixmap)

    def on_button_1_clicked(self):
        log.debug("clicked: B1: {}".format(self.winId()))
        # self.start_info_lcd(pause=0)

    def on_button_2_clicked(self):
        log.debug("clicked: B2: {}".format(self.winId()))

        if not (self.status_lnd_pid_ok and self.status_lnd_listen_ok):
            log.warning("LND is not ready")
            self.ui.error_label.show()
            self.ui.error_label.setText("Err: LND is not ready!")
            self.ui.buttonBox_close.show()
            return

        if not self.status_lnd_unlocked:
            log.warning("LND is locked")
            self.ui.error_label.show()
            self.ui.error_label.setText("Err: LND is locked")
            self.ui.buttonBox_close.show()
            return

        data = self.get_node_uri()
        if data:
            self.show_qr_code(data, SCREEN_NODE_URI)
        else:
            log.warning("Node URI is none!")
            # TODO(frennkie) inform user

    def on_button_3_clicked(self):
        log.debug("clicked: B3: {}".format(self.winId()))

        if not (self.status_lnd_pid_ok and self.status_lnd_listen_ok):
            log.warning("LND is not ready")
            self.ui.error_label.show()
            self.ui.error_label.setText("Err: LND is not ready!")
            self.ui.buttonBox_close.show()
            return

        if not self.status_lnd_unlocked:
            log.warning("LND is locked")
            self.ui.error_label.show()
            self.ui.error_label.setText("Err: LND is locked")
            self.ui.buttonBox_close.show()
            return

        if not self.status_lnd_channel_total_active:
            log.warning(
                "not creating invoice: unable to receive - no open channels")
            self.ui.error_label.show()
            self.ui.error_label.setText("Err: No open channels!")
            self.ui.buttonBox_close.show()
            return

        if not self.status_lnd_channel_total_remote_balance:
            log.warning(
                "not creating invoice: unable to receive - no remote capacity on any channel"
            )
            self.ui.error_label.show()
            self.ui.error_label.setText("Err: No remote capacity!")
            self.ui.buttonBox_close.show()
            return

        dialog_b1 = QDialog(flags=(Qt.Dialog | Qt.FramelessWindowHint))
        ui = Ui_DialogSelectInvoice()
        ui.setupUi(dialog_b1)

        dialog_b1.move(0, 0)

        ui.buttonBox.button(QDialogButtonBox.Yes).setText("{} SAT".format(
            self.rb_cfg.invoice_default_amount))
        ui.buttonBox.button(QDialogButtonBox.Ok).setText("Donation")
        if self.rb_cfg.invoice_allow_donations:
            ui.buttonBox.button(QDialogButtonBox.Ok).setEnabled(True)
        else:
            ui.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False)

        ui.buttonBox.button(QDialogButtonBox.Cancel).setText("Cancel")

        ui.buttonBox.button(QDialogButtonBox.Yes).clicked.connect(
            self.b3_invoice_set_amt)
        ui.buttonBox.button(QDialogButtonBox.Ok).clicked.connect(
            self.b3_invoice_custom_amt)

        dialog_b1.show()

        rsp = dialog_b1.exec_()
        if not rsp == QDialog.Accepted:
            log.info("B3: pressed is: Cancel")

    def b3_invoice_set_amt(self):
        log.info("b1 option: set amount")

        check_invoice_thread = ClockStoppableThread(
            Event(), interval=INVOICE_CHECK_INTERVAL)
        check_invoice_thread.signal.connect(self.check_invoice)
        check_invoice_thread.start()

        a, n = adjective_noun_pair()
        inv_memo = "RB-{}-{}".format(a.capitalize(), n.capitalize())

        new_invoice = self.create_new_invoice(
            inv_memo, amt=self.rb_cfg.invoice_default_amount)
        data = new_invoice.payment_request
        self.show_qr_code(data,
                          SCREEN_INVOICE,
                          memo=inv_memo,
                          status="Open",
                          inv_amt=self.rb_cfg.invoice_default_amount)

    def b3_invoice_custom_amt(self):
        log.info("b1 option: custom amount")

        check_invoice_thread = ClockStoppableThread(
            Event(), interval=INVOICE_CHECK_INTERVAL)
        check_invoice_thread.signal.connect(self.check_invoice)
        check_invoice_thread.start()

        a, n = adjective_noun_pair()
        inv_memo = "RB-{}-{}".format(a.capitalize(), n.capitalize())

        new_invoice = self.create_new_invoice(inv_memo, amt=0)
        data = new_invoice.payment_request
        self.show_qr_code(data,
                          SCREEN_INVOICE,
                          memo=inv_memo,
                          status="Open",
                          inv_amt="Donation")

    def on_button_4_clicked(self):
        log.debug("clicked: B4: {}".format(self.winId()))

        dialog_b4 = QDialog(flags=(Qt.Dialog | Qt.FramelessWindowHint))
        ui = Ui_DialogConfirmOff()
        ui.setupUi(dialog_b4)

        dialog_b4.move(0, 0)

        ui.buttonBox.button(QDialogButtonBox.Yes).setText("Shutdown")
        ui.buttonBox.button(QDialogButtonBox.Retry).setText("Restart")
        ui.buttonBox.button(QDialogButtonBox.Cancel).setText("Cancel")

        ui.buttonBox.button(QDialogButtonBox.Yes).clicked.connect(
            self.b4_shutdown)
        ui.buttonBox.button(QDialogButtonBox.Retry).clicked.connect(
            self.b4_restart)

        dialog_b4.show()
        rsp = dialog_b4.exec_()

        if rsp == QDialog.Accepted:
            log.info("B4: pressed is: Accepted - Shutdown or Restart")
        else:
            log.info("B4: pressed is: Cancel")

    def b4_shutdown(self):
        log.info("shutdown")
        if IS_WIN32_ENV:
            log.info("skipping on win32")
            return

        process = QProcess(self)
        process.start('xterm', [
            '-fn', 'fixed', '-into',
            str(int(self.ui.widget.winId())), '+sb', '-hold', '-e',
            'bash -c \"sudo /home/admin/XXshutdown.sh\"'
        ])

    def b4_restart(self):
        log.info("restart")
        if IS_WIN32_ENV:
            log.info("skipping on win32")
            return

        process = QProcess(self)
        process.start('xterm', [
            '-fn', 'fixed', '-into',
            str(int(self.ui.widget.winId())), '+sb', '-hold', '-e',
            'bash -c \"sudo /home/admin/XXshutdown.sh reboot\"'
        ])

    def create_new_invoice(self, memo="Pay to RaspiBlitz", amt=0):
        if IS_DEV_ENV:
            # Fake an invoice for dev
            class FakeAddInvoiceResponse(object):
                def __init__(self):
                    self.add_index = 145
                    self.payment_request = "lnbc47110n1pwmfqcdpp5k55n5erv60mg6u4c8s3qggnw3dsn267e80ypjxxp6gj593" \
                                           "p3c25sdq9vehk7cqzpgprn0ytv6ukxc2vclgag38nmsmlyggmd4zand9qay2l3gc5at" \
                                           "ecxjynydyzhvxsysam9d46y5lgezh2nkufvn23403t3tz3lyhd070dgq625xp0"
                    self.r_hash = b'\xf9\xe3(\xf5\x84\xdad\x88\xe4%\xa7\x1c\x95\xbe\x8baJ\x1c\xc1\xad*\xed\xc8' \
                                  b'\x158\x13\xdf\xffF\x9c\x95\x84'

            new_invoice = FakeAddInvoiceResponse()

        else:
            with InvoiceStub(network=self.rb_cfg.network,
                             chain=self.rb_cfg.chain) as stub_invoice:
                new_invoice = create_invoice(stub_invoice, memo, amt)

        log.info("#{}: {}".format(new_invoice.add_index,
                                  new_invoice.payment_request))

        invoice_r_hash_hex_str = convert_r_hash_hex_bytes(new_invoice.r_hash)
        self.invoice_to_check = invoice_r_hash_hex_str
        log.info("noting down for checking: {}".format(invoice_r_hash_hex_str))

        return new_invoice

    def get_node_uri(self):
        if IS_DEV_ENV:
            return "535f209faaea75427949e3e6c1fc9edafbf751f08706506bb873fdc93ffc2d4e2c@pqcjuc47eqcv6mk2.onion:9735"

        with ReadOnlyStub(network=self.rb_cfg.network,
                          chain=self.rb_cfg.chain) as stub_readonly:

            res = get_node_uri(stub_readonly)
            log.info("Node URI: {}".format(res))

            return res