diff --git a/__init__.py b/__init__.py index 8b13789..e69de29 100644 --- a/__init__.py +++ b/__init__.py @@ -1 +0,0 @@ - diff --git a/bal.py b/bal.py index e516c00..0c07e41 100644 --- a/bal.py +++ b/bal.py @@ -107,6 +107,8 @@ class BalPlugin(BasePlugin): self.ALLOW_REPUSH = BalConfig(config, "bal_allow_repush", True) self.FIRST_EXECUTION = BalConfig(config, "bal_first_execution", True) self.WELIST_SERVER = BalConfig(config,"bal_welist_server","https://welist.bitcoin-after.life/") + self.EVENT_DESCRIPTION = BalConfig(config,"bal_event_description", "Will execution for $wallet_name\n $heirs_complete\n") + self.EVENT_SUMMARY = BalConfig(config,"bal_event_summary", "Will execution of $wallet_name\n") self.WILLEXECUTORS = BalConfig( config, "bal_willexecutors", @@ -184,9 +186,62 @@ class BalPlugin(BasePlugin): @staticmethod def default_will_settings(): + will_settings ={"baltx_fees":100} + will_settings.update(BalPlugin.default_will_settings_absolute()) + return will_settings + @staticmethod + def default_will_settings_absolute(): + relative_dates=BalPlugin.default_will_settings_relative() today = date.today() dt = datetime(today.year, today.month, today.day, 0, 0, 0) - threshold =(dt + timedelta(days=180)).timestamp() - locktime =(dt + timedelta(days=365)).timestamp() + threshold =(dt + timedelta(days=BalTimestamp(relative_dates["threshold"]).duration_to_days())).timestamp() + locktime =(dt + timedelta(days=BalTimestamp(relative_dates["locktime"]).duration_to_days())).timestamp() + return {"threshold": threshold, "locktime": locktime} + @staticmethod + def default_will_settings_relative(): + return {"threshold" : "30d", "locktime": "1y"} - return {"baltx_fees": 100, "threshold": threshold, "locktime": locktime} + +class BalTimestamp: + value = None + unit = None + def __init__(self,value): + str_value = str(value) + if str_value and str_value[-1].lower() in ("y","d"): + self.value = int(str_value[:-1]) + self.unit = str_value[-1] + else: + try: + self.value = int(value) + except Exception as _e: + self.value=1 + self.unit = None + + def duration_to_days(self): + return self.value*365 if self.unit=='y' else self.value + + def to_date(self,from_date=None,reverse=False): + if self.unit is None: + return datetime.fromtimestamp(self.value) + else: + if from_date is None: + from_date = datetime.now() + if isinstance(from_date, (int, float)): + from_date = datetime.fromtimestamp(from_date) + reverse = 1 if not reverse else -1 + return (from_date + (reverse * timedelta(days = self.duration_to_days()))).replace(hour=0,minute=0,second=0,microsecond=0) + + def to_timestamp(self,from_date=None,reverse=False): + return self.to_date(from_date,reverse).timestamp() + + def __str__(self): + if self.unit is None: + return datetime.fromtimestamp(self.value).isoformat() + else: + return f"{self.value}{self.unit}" + + def __repr__(self): + if self.unit is None: + return datetime.fromtimestamp(self.value).to_date().timestamp() + else: + return f"{self.value}{self.unit}" diff --git a/qt.py b/qt.py index 59faedf..77eb867 100644 --- a/qt.py +++ b/qt.py @@ -5,135 +5,67 @@ BAL Bitcoin after life """ -import subprocess -import os + import copy import enum -import sys +import os +import subprocess +import tempfile import time import traceback -from datetime import datetime,timedelta,timezone +from datetime import datetime, timezone from decimal import Decimal from functools import partial -from typing import TYPE_CHECKING, Any, Callable, Mapping, Optional, Union +from typing import Any, Callable, Mapping, Optional, Union -from electrum.gui.qt.util import getSaveFileName -from electrum.util import FileExportFailed -from typing import Any, Optional, Union -from PyQt6.QtWidgets import QWidget, QHBoxLayout, QLabel, QComboBox, QLineEdit, QDateTimeEdit -from PyQt6.QtCore import pyqtSignal, QDateTime, Qt -from PyQt6.QtGui import QPainter -#from PyQt6.QtStyle import QStyle, QStyleOptionFrame -import tempfile - -#DELETEME - -from PyQt6.QtCore import ( - QDateTime, - QModelIndex, - QPersistentModelIndex, - Qt, - pyqtSignal, -) -from PyQt6.QtGui import ( - QColor, - QPainter, - QPalette, - QStandardItem, - QStandardItemModel, -) -from PyQt6.QtWidgets import ( - QAbstractItemView, - QCheckBox, - QComboBox, - QDateTimeEdit, - QGridLayout, - QHBoxLayout, - QLabel, - QLineEdit, - QMenu, - QMenuBar, - QPushButton, - QScrollArea, - QSizePolicy, - QSpinBox, - QStyle, - QStyleOptionFrame, - QVBoxLayout, - QWidget, -) - -from electrum.bitcoin import ( - NLOCKTIME_BLOCKHEIGHT_MAX, - NLOCKTIME_MAX, - NLOCKTIME_MIN, -) -from electrum.gui.qt.amountedit import ( - BTCAmountEdit, -) - -if TYPE_CHECKING: - from electrum.gui.qt.main_window import ElectrumWindow - -from electrum.gui.qt.main_window import StatusBarButton +from electrum.bitcoin import (NLOCKTIME_BLOCKHEIGHT_MAX, NLOCKTIME_MAX, + NLOCKTIME_MIN) +from electrum.gui.qt.amountedit import BTCAmountEdit +from electrum.gui.qt.main_window import ElectrumWindow, StatusBarButton from electrum.gui.qt.my_treeview import MyTreeView from electrum.gui.qt.password_dialog import PasswordDialog from electrum.gui.qt.transaction_dialog import TxDialog -from electrum.gui.qt.util import ( - Buttons, - CancelButton, - ColorScheme, - EnterButton, - HelpButton, - MessageBoxMixin, - OkButton, - TaskThread, - WindowModalDialog, - char_width_in_lineedit, - import_meta_gui, - read_QIcon_from_bytes, - read_QPixmap_from_bytes, -) +from electrum.gui.qt.util import (Buttons, CancelButton, ColorScheme, + EnterButton, HelpButton, MessageBoxMixin, + OkButton, TaskThread, WindowModalDialog, + char_width_in_lineedit, getSaveFileName, + import_meta_gui, read_QIcon_from_bytes, + read_QPixmap_from_bytes) from electrum.i18n import _ -from electrum.logging import Logger, get_logger +from electrum.logging import get_logger from electrum.network import BestEffortRequestFailed, Network, TxBroadcastError from electrum.payment_identifier import PaymentIdentifier -from electrum.plugin import hook, run_hook +from electrum.plugin import hook from electrum.transaction import SerializationError, Transaction, tx_from_any -from electrum.util import ( - DECIMAL_POINT, - FileImportFailed, - UserCancelled, - decimal_point_to_base_unit_name, - read_json_file, - write_json_file, -) +from electrum.util import (DECIMAL_POINT, FileExportFailed, UserCancelled, + decimal_point_to_base_unit_name, read_json_file, + write_json_file) +from PyQt6.QtCore import (QDateTime, QModelIndex, QPersistentModelIndex, Qt, + QTimer, pyqtSignal) +from PyQt6.QtGui import (QColor, QPainter, QPalette, QStandardItem, + QStandardItemModel) +from PyQt6.QtWidgets import (QAbstractItemView, QCheckBox, QComboBox, + QDateTimeEdit, QGridLayout, QHBoxLayout, QLabel, + QLineEdit, QTextEdit, QMenu, QMenuBar, QPushButton, + QScrollArea, QSizePolicy, QSpinBox, + QStackedWidget, QStyle, QStyleOptionFrame, + QVBoxLayout, QWidget) -from .bal import BalPlugin +from .bal import BalPlugin,BalTimestamp from .heirs import HEIR_DUST_AMOUNT, HEIR_REAL_AMOUNT, Heirs -from .util import Util -from .will import ( - AmountException, - HeirChangeException, - HeirNotFoundException, - NoHeirsException, - NotCompleteWillException, - NoWillExecutorNotPresent, - TxFeesChangedException, - Will, - WillexecutorChangeException, - WillExecutorNotPresent, - WillExpiredException, - WillItem, -) +from .util import Util +from .will import (AmountException, HeirChangeException, HeirNotFoundException, + NoHeirsException, NotCompleteWillException, + NoWillExecutorNotPresent, TxFeesChangedException, Will, + WillexecutorChangeException, WillExecutorNotPresent, + WillExpiredException, WillItem) from .willexecutors import Willexecutors _logger = get_logger(__name__) -class Plugin(BalPlugin, Logger): +class Plugin(BalPlugin): def __init__(self, parent, config, name): - Logger.__init__(self) _logger.info("INIT BALPLUGIN") BalPlugin.__init__(self, parent, config, name) self.bal_windows = {} @@ -151,8 +83,10 @@ class Plugin(BalPlugin, Logger): title=_("Success"), ) return - w = BalWindow(self, window) - self.bal_windows[window.winId] = w + Util.print_var(window) + top_level_window= window.top_level_window + w = BalWindow(self, top_level_window) + self.bal_windows[top_level_window.winId] = w for child in window.children(): if isinstance(child, QMenuBar): for menu_child in child.children(): @@ -220,8 +154,9 @@ class Plugin(BalPlugin, Logger): def get_window(self, window): w = self.bal_windows.get(window.winId, None) if w is None: - w = BalWindow(self, window) - self.bal_windows[window.winId] = w + win=window.top_level_window() + w = BalWindow(self, win) + self.bal_windows[win.winId] = w return w def requires_settings(self): @@ -262,24 +197,22 @@ class Plugin(BalPlugin, Logger): lbl_logo = QLabel() lbl_logo.setPixmap(qicon) - # heir_ping_willexecutors = bal_checkbox(self.PING_WILLEXECUTORS) - # heir_ask_ping_willexecutors = bal_checkbox(self.ASK_PING_WILLEXECUTORS) - # heir_no_willexecutor = bal_checkbox(self.NO_WILLEXECUTOR) + # heir_ping_willexecutors = BalCheckBox(self.PING_WILLEXECUTORS) + # heir_ask_ping_willexecutors = BalCheckBox(self.ASK_PING_WILLEXECUTORS) + # heir_no_willexecutor = BalCheckBox(self.NO_WILLEXECUTOR) def on_multiverse_change(): self.update_all() - # heir_enable_multiverse = bal_checkbox(self.ENABLE_MULTIVERSE,on_multiverse_change) + # heir_enable_multiverse = BalCheckBox(self.ENABLE_MULTIVERSE,on_multiverse_change) - heir_hide_replaced = bal_checkbox(self.HIDE_REPLACED, on_multiverse_change) + heir_hide_replaced = BalCheckBox(self.HIDE_REPLACED, on_multiverse_change) - heir_hide_invalidated = bal_checkbox( - self.HIDE_INVALIDATED, on_multiverse_change - ) + heir_hide_invalidated = BalCheckBox(self.HIDE_INVALIDATED, on_multiverse_change) heir_repush = QPushButton("Rebroadcast transactions") heir_repush.clicked.connect(partial(self.broadcast_transactions, True)) bal_mode = QComboBox() - options =["Easy","Advanced","Experimental"] + options = ["Easy", "Advanced", "Experimental"] bal_mode.addItems(options) grid = QGridLayout(d) @@ -287,7 +220,7 @@ class Plugin(BalPlugin, Logger): grid, "Hide Replaced", heir_hide_replaced, - 1, + 1, "Hide replaced transactions from will detail and list", ) add_widget( @@ -297,9 +230,40 @@ class Plugin(BalPlugin, Logger): 2, "Hide invalidated transactions from will detail and list", ) - add_widget(grid,"Calendar App", QLineEdit(self.CALENDAR_APP.get()),3,"Default app used to open calendar",) - - add_widget(grid,"Bal Mode",bal_mode,4,"choose bal mode") + add_widget( + grid, + "Calendar App", + BalLineEdit(self.CALENDAR_APP), + 3, + "Default app used to open calendar", + ) + add_widget( + grid, + "Event summary", + BalLineEdit(self.EVENT_SUMMARY), + 4, + ( + "Default message to be used in event summary\n" + "Variables:\n" + " $wallet_name: name of wallet\n" + " $heirs_complete: list of heirs name,address,amount\n" + #" $will_details_complete: will details(id transaction, mining fees, willexecutor, willexecutor fees, locktime)\n" + ) + ) + add_widget( + grid, + "Event sescription", + BalTextEdit(self.EVENT_DESCRIPTION), + 5, + ( + "Default message to be used in event description\n" + "Variables:\n" + " $wallet_name: name of wallet\n" + " $heirs_complete: list of heirs name,address,amount\n" + #" $will_details_complete: will details(id transaction, mining fees, willexecutor, willexecutor fees, locktime)\n" + ) + ) + #add_widget(grid, "Bal Mode", bal_mode, 4, "choose bal mode") # add_widget( # grid, @@ -365,8 +329,7 @@ class shown_cv: self.value = value -class BalWindow(): - +class BalWindow: def __init__(self, bal_plugin: "BalPlugin", window: "ElectrumWindow"): self.bal_plugin = bal_plugin self.window = window @@ -384,6 +347,7 @@ class BalWindow(): if not self.will_settings: self.will_settings = self.bal_plugin.WILL_SETTINGS.get() Util.fix_will_settings_tx_fees(self.will_settings) + self.heirs = Heirs(self.wallet) self.heirs_tab = self.create_heirs_tab() self.will_tab = self.create_will_tab() @@ -433,8 +397,8 @@ class BalWindow(): for wid, w in self.will.items(): self.willitems[wid] = WillItem(w, wallet=self.wallet) if self.willitems: - self.will_list.will = self.willitems - self.will_list.update_will(self.willitems) + self.will_list_widget.will = self.willitems + self.will_list_widget.update_will(self.willitems) self.will_tab.update() def save_willitems(self): @@ -452,6 +416,7 @@ class BalWindow(): ) if not self.heirs: self.heirs = Heirs._validate(Heirs(self.wallet)) + self.heirs_tab.update() if not self.will: self.will = self.wallet.db.get_dict("will") Util.fix_will_tx_fees(self.will) @@ -468,7 +433,7 @@ class BalWindow(): self.close_wallet() return - #if not self.will_settings: + # if not self.will_settings: # self.will_settings = self.wallet.db.get_dict("will_settings") # Util.fix_will_settings_tx_fees(self.will_settings) @@ -489,15 +454,16 @@ class BalWindow(): self.willexecutor_dialog.show() def create_heirs_tab(self): + if not self.heirs: + self.heirs = Heirs(self.wallet) self.heir_list_widget = HeirListWidget(self, self.window) - tab = self.window.create_list_tab(self.heir_list_widget) tab.is_shown_cv = shown_cv(False) return tab def create_will_tab(self): - self.will_list = PreviewList(self, self.window, None) - tab = self.window.create_list_tab(self.will_list) + self.will_list_widget = PreviewList(self, self.window, None) + tab = self.window.create_list_tab(self.will_list_widget) tab.is_shown_cv = shown_cv(True) return tab @@ -516,20 +482,17 @@ class BalWindow(): heir_name = QLineEdit() heir_name.setFixedWidth(32 * char_width_in_lineedit()) - if heir: - heir_name.setText(str(heir_key)) heir_address = QLineEdit() heir_address.setFixedWidth(32 * char_width_in_lineedit()) - if heir: - heir_address.setText(str(heir[0])) heir_amount = PercAmountEdit(self.window.get_decimal_point) + if heir: + heir_name.setText(str(heir_key)) + heir_address.setText(str(heir[0])) heir_amount.setText( str(Util.decode_amount(heir[1], self.window.get_decimal_point())) ) - self.heir_locktime = LockTimeWidget(self.bal_window, self, heir[2]) - if heir: - self.heir_locktime.set_value(heir[2]) + self.heir_locktime = LockTimeWidget(self, self.window, heir[2]) # heir_is_xpub = QCheckBox() @@ -573,7 +536,7 @@ class BalWindow(): heir_name.text(), heir_address.text(), Util.encode_amount(heir_amount.text(), self.window.get_decimal_point()), - str(self.heir_locktime.get_value()), + str(self.will_settings["locktime"]), ] try: self.set_heir(heir) @@ -606,7 +569,10 @@ class BalWindow(): def import_heirs(self): import_meta_gui( - self.window, _("heirs"), self.heirs.import_file, self.heir_list_widget.update + self.window, + _("heirs"), + self.heirs.import_file, + self.heir_list_widget.update, ) def export_heirs(self): @@ -632,7 +598,6 @@ class BalWindow(): # willtodelete = [] # willtoappend = {} try: - self.init_class_variables() self.willexecutors = Willexecutors.get_willexecutors( self.bal_plugin, update=False, bal_window=self ) @@ -712,22 +677,48 @@ class BalWindow(): def show_critical(self, text): self.window.show_critical(text) - def update_setting_widgets(self,new_value,field,update_all=False,update_will_dialog=False,update_heirs_dialog=False,): - new_value = self.will_settings[field] = new_value if new_value else BalPlugin.default_will_settings()[field] - self.will_settings[field]=new_value - self.bal_plugin.WILL_SETTINGS.set(self.will_settings) - if update_all or update_will_dialog: - try: - self.will_list.will_settings_widget.widgets[field].set_value(new_value) - except Exception as e: - _logger.error(f"error setting will_settings_widgets[{field}].set_value({new_value})") + def update_combo_setting_widgets( + self, + new_value, + field, + update_all=False, + update_will_dialog=False, + update_heirs_dialog=False, + ): + if (update_all or update_will_dialog) and hasattr(self,'will_list_widget'): + self.update_widget_combo(self.will_list_widget,field,new_value) + if update_all or update_heirs_dialog and hasattr(self,'heir_list_widget'): + self.update_widget_combo(self.heir_list_widget,field,new_value) + + + def update_widget_combo(self,widget,field,value): + try: + widget.will_settings_widget.widgets[field].set_index(value) + except Exception as _e: + pass + def update_widget_value(self, widget, field, value): + try: + widget.will_settings_widget.widgets[field].set_value(value) + except Exception as _e: + pass + + def update_setting_widgets( + self, + new_value, + field, + update_all=False, + update_will_dialog=False, + update_heirs_dialog=False, + ): if update_all or update_heirs_dialog: - try: - self.heir_list_widget.will_settings_widget.widgets[field].set_value(new_value) - except Exception as e: - _logger.error(f"error updating settings widget {e}") + self.update_widget_value(self.heir_list_widget, field, new_value) + if update_all or update_will_dialog: + self.update_widget_value(self.will_list_widget, field, new_value) + self.will_settings[field] = new_value + self.bal_plugin.WILL_SETTINGS.set(self.will_settings) def init_heirs_to_locktime(self, multiverse=False): + #pass for heir in self.heirs: h = self.heirs[heir] if not multiverse: @@ -737,24 +728,22 @@ class BalWindow(): if not self.heirs: raise NoHeirsException(_("Heirs are not defined")) try: - self.date_to_check = Util.parse_locktime_string( - self.will_settings["threshold"] - ) + self.date_to_check = BalTimestamp(self.will_settings['threshold']).to_timestamp() # found = False self.locktime_blocks = self.bal_plugin.LOCKTIME_BLOCKS.get() self.current_block = Util.get_current_height(self.wallet.network) - self.block_to_check=0 + self.block_to_check = 0 self.no_willexecutor = self.bal_plugin.NO_WILLEXECUTOR.get() self.willexecutors = Willexecutors.get_willexecutors( self.bal_plugin, update=True, bal_window=self, task=False ) if self.date_to_check < datetime.now().timestamp(): - raise CheckAliveException(self.date_to_check) + raise CheckAliveError(self.date_to_check) self.init_heirs_to_locktime(self.bal_plugin.ENABLE_MULTIVERSE.get()) - except Exception as e: + log_error(e, self) _logger.error(f"init_class_variables: {e}") raise e @@ -782,8 +771,12 @@ class BalWindow(): f"In the inheritance process, the entire wallet will always be fully emptied. Your settings require an adjustment of the amounts.{e}" ) ) - except CheckAliveException: - self.show_error(_("CheckAlive is in the past please update it to a date in the future but less than locktime")) + except CheckAliveError: + self.show_error( + _( + "CheckAlive is in the past please update it to a date in the future but less than locktime" + ) + ) return locktime = Util.parse_locktime_string(self.will_settings["locktime"]) if locktime < self.date_to_check: @@ -902,7 +895,8 @@ class BalWindow(): self.show_message(_("No transactions to invalidate")) def on_failure(exec_info): - log_error(exec_info,self.bal_window) + log_error(exec_info, self.bal_window) + fee_per_byte = self.will_settings.get("baltx_fees", 1) task = partial(Will.invalidate_will, self.willitems, self.wallet, fee_per_byte) msg = _("Calculating Transactions") @@ -995,7 +989,7 @@ class BalWindow(): self.willitems[txid].tx = copy.deepcopy(tx) self.will[txid] = self.willitems[txid].to_dict() try: - self.will_list.update() + self.will_list_widget.update() except Exception: pass if callback: @@ -1005,7 +999,7 @@ class BalWindow(): raise e def on_failure(exec_info): - log_error(exec_info,self.bal_window) + log_error(exec_info, self.bal_window) password = self.get_wallet_password() task = partial(self.sign_transactions, password) @@ -1017,7 +1011,7 @@ class BalWindow(): def broadcast_transactions(self, force=False): def on_success(sulcess): - self.will_list.update() + self.will_list_widget.update() if sulcess: _logger.info("error, some transaction was not sent") self.show_warning(_("Some transaction was not broadcasted")) @@ -1028,13 +1022,13 @@ class BalWindow(): ) def on_failure(exec_info): - log_error(exec_info,self.bal_window) - #a,b,c = err - #_logger.error(f"fail to broadcast transactions:{err}") - #_logger.error(f"error: {b}") - #_logger.error("traceback ") - #tb = c - #while tb is not None: + log_error(exec_info, self.bal_window) + # a,b,c = err + # _logger.error(f"fail to broadcast transactions:{err}") + # _logger.error(f"error: {b}") + # _logger.error("traceback ") + # tb = c + # while tb is not None: # frame = tb.tb_frame # _logger.error("file:", frame.f_code.co_filename) # _logger.error("name:", frame.f_code.co_name) @@ -1085,10 +1079,7 @@ class BalWindow(): ) w = self.willitems[wid] w.set_check_willexecutor( - Willexecutors.check_transaction( - wid, - w.we["url"] - ) + Willexecutors.check_transaction(wid, w.we["url"]) ) self.waiting_dialog.update( "checked {} - {} : {}".format( @@ -1116,7 +1107,7 @@ class BalWindow(): def import_will(self): def sulcess(): - self.will_list.update_will(self.willitems) + self.will_list_widget.update_will(self.willitems) import_meta_gui(self.window, _("will"), self.import_json_file, sulcess) @@ -1130,7 +1121,7 @@ class BalWindow(): self.update_will(willitems) except Exception as e: raise e - raise FileImportFailed(_("Invalid will file")) + # raise FileImportFailed(_("Invalid will file")) def check_transactions_task(self, will): start = time.time() @@ -1141,7 +1132,6 @@ class BalWindow(): w.set_check_willexecutor(Willexecutors.check_transaction(wid, w.we["url"])) - if time.time() - start < 3: time.sleep(3 - (time.time() - start)) @@ -1152,9 +1142,9 @@ class BalWindow(): pass def on_failure(exec_info): - log_error(exec_info,self.bal_window) - #_logger.error(f"error checking transactions {e}") - #pass + log_error(exec_info, self) + # _logger.error(f"error checking transactions {e}") + # pass task = partial(self.check_transactions_task, will) msg = _("Check Transaction") @@ -1163,7 +1153,7 @@ class BalWindow(): ) self.waiting_dialog.exe() - def update_willexecutor_list_widget(self,parent,willexecutors): + def update_willexecutor_list_widget(self, parent, willexecutors): try: parent.willexecutors_list.update(willexecutors) parent.will_executor_list_widget.update() @@ -1171,23 +1161,25 @@ class BalWindow(): _logger.error(f"impossible to update will_executor_list_widget {e}") self.will_executors.update() - def download_list(self,willexecutors,fn_on_success,fn_on_failure=None): + def download_list(self, willexecutors, fn_on_success, fn_on_failure=None): def on_success(result): - #self.willexecutors.update(result) + # self.willexecutors.update(result) fn_on_success(result) + def on_failure(exec_info): fn_on_failure(exec_info) if not fn_on_failure: - fn_on_success=log_error + fn_on_success = log_error welist_server = self.bal_plugin.WELIST_SERVER.get() - task = partial(Willexecutors.download_list,willexecutors,welist_server) + task = partial(Willexecutors.download_list, willexecutors, welist_server) msg = _(f"Downloadinf willexecutors list from {welist_server}") self.waiting_dialog = BalWaitingDialog( self, msg, task, on_success, on_failure, exe=False ) self.waiting_dialog.exe() + def ping_willexecutors_task(self, wes): _logger.info("ping willexecutots task") pinged = [] @@ -1220,14 +1212,15 @@ class BalWindow(): else: pinged.append(url) - def ping_willexecutors(self, wes, fn_on_success,fn_on_failure=None): + def ping_willexecutors(self, wes, fn_on_success, fn_on_failure=None): def on_success(result): fn_on_success(result) def on_failure(exec_info): fn_on_failure(exec_info) + if not fn_on_failure: - fn_on_failure=log_error + fn_on_failure = log_error _logger.info("ping willexecutors") task = partial(self.ping_willexecutors_task, wes) msg = _("Ping Will-Executors") @@ -1245,14 +1238,12 @@ class BalWindow(): Will.add_willtree(self.willitems) all_utxos = self.wallet.get_utxos() utxos_list = Will.utxos_strs(all_utxos) - Will.check_invalidated( - self.willitems, utxos_list, self.wallet - ) + Will.check_invalidated(self.willitems, utxos_list, self.wallet) - self.will_list.update_will(self.willitems) + self.will_list_widget.update_will(self.willitems) self.heirs_tab.update() self.will_tab.update() - self.will_list.update() + self.will_list_widget.update() except Exception as e: _logger.error(f"error while updating window: {e}") @@ -1263,41 +1254,83 @@ def add_widget(grid, label, widget, row, help_): grid.addWidget(HelpButton(help_), row, 2) +class ClickableLabel(QLabel): + doubleClicked = pyqtSignal() + + def mouseDoubleClickEvent(self, event): + self.doubleClicked.emit() + super().mouseDoubleClickEvent(event) + class BalTxFeesWidget(QWidget): valueChanged = pyqtSignal() - def __init__(self,bal_window,parent,value=None): + current_value = None + + def __init__(self, bal_window, parent, value=None): super().__init__(parent) - self.bal_window=bal_window + self.bal_window = bal_window layout = QHBoxLayout(self) - self.txfee_widget=QSpinBox(self) + self.txfee_widget = QSpinBox(self) self.txfee_widget.setMinimum(1) self.txfee_widget.setMaximum(10000) - value = value if value else self.bal_window.will_settings['baltx_fees'] - self.txfee_widget.setValue(value) + value = ( + value + if value + else self.bal_window.bal_plugin.WILL_SETTINGS.get()["baltx_fees"] + ) + self.set_value(value) + self.default_value = self.bal_window.bal_plugin.default_will_settings()[ + "baltx_fees" + ] self.txfee_widget.valueChanged.connect(self.on_heir_tx_fees) - layout.addWidget(QLabel("Tx Fees:")) + label = ClickableLabel("Tx Fees:") + label.doubleClicked.connect(self.doubleclick) + layout.addWidget(label) layout.addWidget(self.txfee_widget) + def doubleclick(self, event=None): + pass def get_value(self): return self.txfee_widget.value() - def set_value(self,value): - if not value: - value=self.bal_window.bal_plugin.default_will_settings()['baltx_fees'] - return self.txfee_widget.setValue(value) + def set_value(self, value, emit=True): + value = int(value) if value is not None else 20 + if getattr(self, "_updating", False): + return - def on_heir_tx_fees(self,update_all=False): + self._updating = True try: - if update_all: - self.bal_window.update_setting_widgets(self.get_value(),'baltx_fees',True) - except Exception as e: - _logger.error("error while trying to update txfees",e ) + self.current_value = value + spin = self.txfee_widget + spin.blockSignals(True) + spin.setValue(value) + spin.blockSignals(False) + + finally: + self._updating = False + + if emit: + spin.valueChanged.emit(value) + + def on_heir_tx_fees(self, value=None, update_all=True): + if value != self.current_value: + try: + self.set_value(value) + if update_all: + self.bal_window.update_setting_widgets( + self.get_value(), "baltx_fees", True + ) + except Exception as e: + _logger.error(f"error while trying to update txfees{e}") + log_error(e) + else: + pass class _LockTimeEditor: min_allowed_value = NLOCKTIME_MIN max_allowed_value = NLOCKTIME_MAX + alarm = None def get_value(self) -> Optional[int]: raise NotImplementedError() @@ -1311,9 +1344,10 @@ class _LockTimeEditor: return True try: x = int(x) - except Exception: + except Exception as _e: return False return cls.min_allowed_value <= x <= cls.max_allowed_value + @staticmethod def get_max_allowed_timestamp() -> int: ts = NLOCKTIME_MAX @@ -1326,29 +1360,34 @@ class _LockTimeEditor: datetime.fromtimestamp(ts) # test if raises return ts + class BalTimeEditWidget(QWidget, _LockTimeEditor): valueEdited = pyqtSignal() _setting_locktime = False - _current_value = None + current_value = None + current_index = None + default_value = None - help_text = "if you choose Raw, you can insert various options based on suffix:\n" \ - + " - d: number of days after current day(ex: 1d means tomorrow)\n" \ + help_text = ( + "if you choose Raw, you can insert various options based on suffix:\n" + + " - d: number of days after current day(ex: 1d means tomorrow)\n" + " - y: number of years after currrent day(ex: 1y means one year from today)\n" - label_text ="locktime" + ) + label_text = None + base_field = None def __init__(self, bal_window, parent, default_locktime=None): super().__init__(parent) - self.bal_window=bal_window + self.bal_window = bal_window hbox = QHBoxLayout() self.setLayout(hbox) hbox.setContentsMargins(0, 0, 0, 0) hbox.setSpacing(0) - self.setMinimumWidth(50*char_width_in_lineedit()) + self.setMinimumWidth(40 * char_width_in_lineedit()) self.locktime_raw_e = TimeRawEditWidget(self, time_edit=self) self.locktime_date_e = LockTimeDateEdit(self, time_edit=self) self.editors = [self.locktime_raw_e, self.locktime_date_e] - self.combo = QComboBox() options = [_("Raw"), _("Date")] self.option_index_to_editor_map = { @@ -1358,103 +1397,124 @@ class BalTimeEditWidget(QWidget, _LockTimeEditor): self.combo.addItems(options) default_index = 0 if not default_locktime: - default_locktime = self.bal_window.will_settings[self.base_field] + default_locktime = self.bal_window.bal_plugin.WILL_SETTINGS.get()[self.base_field] try: int(default_locktime) - default_index=1 - except Exception as e: - default_index=0 + default_index = 1 + except Exception: + default_index = 0 hbox.addWidget(QLabel(self.label_text)) hbox.addWidget(HelpButton(self.help_text)) self.combo.currentIndexChanged.connect(self.on_current_index_changed) - self.editor = self.option_index_to_editor_map[default_index] - self.editor.set_value(default_locktime, force=False) - self.combo.setCurrentIndex(default_index) - self.on_current_index_changed(default_index) + for w in self.editors: + w.setVisible(False) + w.setEnabled(False) + + self.editor = self.option_index_to_editor_map[default_index] + self.editor.setVisible(True) + self.editor.setEnabled(True) + self.set_index(default_index) + #self.on_current_index_changed(default_index) + self.set_value(default_locktime) + self.current_value=default_locktime hbox.addWidget(self.combo) for w in self.editors: hbox.addWidget(w) hbox.addStretch(1) - # spacer_widget = QWidget() + # spssscer_widget = QWidget() # spacer_widget.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) # hbox.addWidget(spacer_widget) self.valueEdited.connect(lambda: self.update_will_settings(True)) self.locktime_raw_e.editingFinished.connect(self.valueEdited.emit) self.locktime_date_e.dateTimeChanged.connect(self.valueEdited.emit) - self.combo.currentIndexChanged.connect(self.valueEdited.emit) + #self.combo.currentIndexChanged.connect(self.valueEdited.emit) + def update_will_settings( + self, + update_all=False, + update_will_dialog=False, + update_heirs_dialog=False, + ): + self.bal_window.update_setting_widgets( + self.get_value(), + self.base_field, + update_all, + update_will_dialog, + update_heirs_dialog, + ) - - def update_will_settings(self,update_all=False,update_will_dialog=False,update_heirs_dialog=False,): - self.bal_window.update_setting_widgets(self.get_value(),self.base_field,update_all,update_will_dialog,update_heirs_dialog) - - def on_current_index_changed(self, i,force=False): + def on_current_index_changed(self, i): + self.current_index = i for w in self.editors: w.setVisible(False) w.setEnabled(False) - #prev_locktime = self.editor.get_value() + # prev_locktime = self.editor.get_value() self.editor = self.option_index_to_editor_map[i] - #if self.editor.is_acceptable_locktime(prev_locktime): - # self.editor.set_value(prev_locktime, force=False) + if i==0: + self.editor.set_value(self.bal_window.bal_plugin.default_will_settings_relative()[self.base_field]) + else: + self.editor.set_value(self.bal_window.bal_plugin.default_will_settings_absolute()[self.base_field]) + self.valueEdited.emit() + # if self.editor.is_acceptable_locktime(prev_locktime): + # self.editor.set_value(prev_locktime, force=False) self.editor.setVisible(True) self.editor.setEnabled(True) + self.bal_window.update_combo_setting_widgets(i, self.base_field,True) def get_value(self) -> Optional[str]: val = self.editor.get_value() + #return self.current_value return val - def set_index(self, index,force = False): - self.combo.setCurrentIndex(index) - self.on_current_index_changed(index,force) + def set_index(self, index): + if self.current_index != index: + self.combo.setCurrentIndex(index) + #self.on_current_index_changed(index, force) - def set_value(self, x: Any, force=None,update_all=False,update_will_dialog=False,update_heirs_dialog=False) -> None: - current_val=self.get_value() - if self._setting_locktime: - return - newtime=x - current = self.get_value() - if x==current: - return + def set_value( + self, + x: Any, + force=None, + update_all=False, + update_will_dialog=False, + update_heirs_dialog=False, + ) -> None: + if not x: + if self.current_index == 0: + x = self.bal_window.bal_plugin.default_will_settings_relative()[self.base_field] + elif self.current_index == 1: + x = self.bal_window.bal_plugin.default_will_settings_absolute()[self.base_field] + if x != self.get_value(): + self.editor.set_value(x) + self.current_value = x + self.bal_window.update_setting_widgets(x, self.base_field) - self._setting_locktime = True - try: - if not newtime: - newtime=self.bal_window.bal_plugin.WILL_SETTINGS.get()[self.base_field] - - try: - int(x) - if force is not None: - self.set_index(1,True) - except Exception: - if isinstance(x,str): - if force is not None: - self.set_index(0,True) - self.current_locktime = newtime - self.editor.set_value(newtime, force=False) - self.update_will_settings(update_all,update_will_dialog,update_heirs_dialog) - finally: - self._setting_locktime = False class TimeRawEditWidget(QWidget): editingFinished = pyqtSignal() - def __init__(self,parent,time_edit=None): + + def is_acceptable_locktime(self, value): + return True + + def __init__(self, parent, time_edit=None): super().__init__(parent) - self.editor=LockTimeRawEdit(parent,time_edit) - self.label=QLabel("") - self.label.setFixedWidth(17 * char_width_in_lineedit()) - self.layout=QHBoxLayout(self) + self.editor = LockTimeRawEdit(parent, time_edit) + self.label = QLabel("") + self.label.setFixedWidth(10 * char_width_in_lineedit()) + self.layout = QHBoxLayout(self) self.layout.addWidget(self.editor) self.layout.addWidget(self.label) self.editor.editingFinished.connect(self.editingFinished.emit) - self.get_value=self.editor.get_value - self.set_value=self.editor.set_value + self.get_value = self.editor.get_value + self.set_value = self.editor.set_value + class LockTimeRawEdit(QLineEdit, _LockTimeEditor): def __init__(self, parent=None, time_edit=None): QLineEdit.__init__(self, parent) - self.setFixedWidth(6 * char_width_in_lineedit()) + self.setFixedWidth(12 * char_width_in_lineedit()) self.textChanged.connect(self.numbify) self.isdays = False self.isyears = False @@ -1508,16 +1568,13 @@ class LockTimeRawEdit(QLineEdit, _LockTimeEditor): self.blockSignals(True) self.setText(s) self.blockSignals(False) - #self.set_value(s, force=False) - self._current_value = s + # self.set_value(s, force=False) + self.current_value = s # setText sets Modified to False. Instead we want to remember # if updates were because of user modification. self.setModified(self.hasFocus()) self.setCursorPosition(pos) - def get_absolute_value(self): - return Util.locktime_to_str(Util.parse_locktime_string(self.get_value())) - def get_value(self) -> Optional[str]: try: return str(self.text()) @@ -1525,9 +1582,12 @@ class LockTimeRawEdit(QLineEdit, _LockTimeEditor): return None def set_value(self, x: Any, force=True) -> None: - self.setText(str(x)) + if x != self.get_value(): + self.blockSignals(True) + self.setText(str(x)) + self.blockSignals(False) + self.numbify() - self.numbify() class LockTimeDateEdit(QDateTimeEdit, _LockTimeEditor): min_allowed_value = NLOCKTIME_BLOCKHEIGHT_MAX + 1 @@ -1537,13 +1597,21 @@ class LockTimeDateEdit(QDateTimeEdit, _LockTimeEditor): QDateTimeEdit.__init__(self, parent) self.setMinimumDateTime(datetime.fromtimestamp(self.min_allowed_value)) self.setMaximumDateTime(datetime.fromtimestamp(self.max_allowed_value)) - self.setDateTime(QDateTime.currentDateTime()) + #self.setDateTime(QDateTime.currentDateTime()) self.time_edit = time_edit def get_value(self) -> Optional[int]: - dt = self.dateTime().toPyDateTime() - locktime = int(time.mktime(dt.timetuple())) - return locktime + #dt = self.dateTime().toPyDateTime() + #locktime = int(time.mktime(dt.timetuple())) + #p# + #dt = dt_edit.dateTime() + ## QDateTimets = dt.toSecsSinceEpoch() + + dt = self.dateTime() + _ts = dt.toSecsSinceEpoch() + + return _ts + def set_value(self, x: Any, force=False) -> None: if not self.is_acceptable_locktime(x): @@ -1551,161 +1619,178 @@ class LockTimeDateEdit(QDateTimeEdit, _LockTimeEditor): return try: x = int(x) - except Exception: - self.setDateTime(QDateTime.currentDateTime()) - return - dt = datetime.fromtimestamp(x) - self.setDateTime(dt) - self.alarm=dt + except Exception as e: + x = QDateTime.currentDateTime().timestamp() + finally: + _dt = datetime.fromtimestamp(x) + #if self.alarm != dt: + self.setDateTime(_dt) + self.alarm = _dt + class ThresholdTimeWidget(BalTimeEditWidget): - help_text = ("Check to ask for invalidation.\n\n" - "When less then this time is missing, ask to invalidate.\n" - "If you fail to invalidate during this time, your transactions will be delivered to your heirs.\n\n" - f"{BalTimeEditWidget.help_text}") - label_text="Check Alive:" + help_text = ( + "Check to ask for invalidation.\n\n" + "When less then this time is missing, ask to invalidate.\n" + "If you fail to invalidate during this time, your transactions will be delivered to your heirs.\n\n" + f"{BalTimeEditWidget.help_text}" + ) + label_text = "Check Alive:" base_field = "threshold" + def __init__(self, bal_window, parent, init_value=None): + if init_value is None: + init_value = bal_window.bal_plugin.WILL_SETTINGS.get()["threshold"] + super().__init__(bal_window, parent, init_value) + self.default_value = self.bal_window.bal_plugin.default_will_settings()[ + "threshold" + ] + + class LockTimeWidget(BalTimeEditWidget): - help_text = ("Set Locktime for transactions.\n" - "Any time is needed transaction will be anticipated by 1day\n" - f"{BalTimeEditWidget.help_text}") + help_text = ( + "Set Locktime for transactions.\n" + "Any time is needed transaction will be anticipated by 1day\n" + f"{BalTimeEditWidget.help_text}" + ) label_text = "Delivery Time:" base_field = "locktime" + def __init__(self, bal_window, parent, init_value=None): + if init_value is None: + init_value = bal_window.bal_plugin.WILL_SETTINGS.get()["locktime"] + super().__init__(bal_window, parent, init_value) + self.default_value = self.bal_window.bal_plugin.default_will_settings()[ + "locktime" + ] + + class WillSettingsWidget(QWidget): - widgets={} - def __init__(self,bal_window,parent,layout_type='h'): - super().__init__(parent) - self.bal_window=bal_window - box=QHBoxLayout(self) if layout_type=='h' else QVBoxLayout(self) - self.calendar_button=QPushButton(_("Calendar")) - self.calendar_button.setIcon(read_QIcon_from_bytes(self.bal_window.bal_plugin.read_file("icons/calendar.png"))) + def __init__(self, bal_window: "BalWindow", parent, layout_type="h"): + self.widgets = {} + QWidget.__init__(self, parent) + self.bal_window = bal_window + box = QHBoxLayout(self) if layout_type == "h" else QVBoxLayout(self) + + self.calendar_button = QPushButton() + self.calendar_button.setIcon( + read_QIcon_from_bytes( + self.bal_window.bal_plugin.read_file("icons/calendar.png") + ) + ) self.calendar_button.clicked.connect(self.open_or_save_calendar) - self.widgets['locktime']=LockTimeWidget(bal_window,self) - self.widgets['threshold']= ThresholdTimeWidget(bal_window,self) - self.widgets['baltx_fees'] =BalTxFeesWidget(bal_window,self) - box.addWidget(self.widgets['locktime']) - box.addWidget(self.widgets['threshold']) - box.addWidget(self.calendar_button) - box.addWidget(self.widgets['baltx_fees']) - self.widgets['locktime'].valueEdited.connect(self.on_locktime_change) - self.widgets['threshold'].valueEdited.connect(self.on_locktime_change) + self.widgets["locktime"] = LockTimeWidget(bal_window, self) + self.widgets["threshold"] = ThresholdTimeWidget(bal_window, self) + self.widgets["locktime"].valueEdited.connect(self.on_locktime_change) + self.widgets["threshold"].valueEdited.connect(self.on_locktime_change) + # self.widgets['baltx_fees'].valueChange.connect(self.bal_window.update_setting_widgets) self.on_locktime_change() - - @staticmethod - def write_temp_ics(content): - fd, path = tempfile.mkstemp(prefix="event_", suffix=".ics") - with os.fdopen(fd, "w", encoding="utf-8") as f: - f.write(content) - return path + self.widgets["baltx_fees"] = BalTxFeesWidget(bal_window, self) + if not hasattr(bal_window, "txfee_widgets"): + bal_window.txfee_widgets = [] - def open_with_default_app(self,path): - try: - subprocess.check_call([self.bal_window.bal_plugin.CALENDAR_APP.get(), path]) - return True - except Exception as e: - return False - @staticmethod - def save_to_cwd(path, filename="evento.ics"): - target = os.path.abspath(filename) - # se il file esiste, sovrascrive - with open(path, "rb") as src, open(target, "wb") as dst: - dst.write(src.read()) - return target - - @staticmethod - def format_time(time): - return time.astimezone(timezone.utc).strftime("%Y%m%dT%H%M%SZ") - - @staticmethod - def ical_escape(text: str) -> str: - # escape per RFC5545: backslash, ; , newlines - text = text.replace("\\", "\\\\").replace(";", r"\;").replace(",", r"\,").replace("\n", r"\n") - return WillSettingsWidget.fold_ical_line(text) - - @staticmethod - def fold_ical_line(line: str, limit: int = 75) -> str: - # ritorna linee separate da CRLF e folding con spazio iniziale sulle righe successive - encoded = line.encode("utf-8") - parts = [] - while len(encoded) > limit: - # taglia senza spezzare byte UTF-8 - cut = limit - while (encoded[cut] & 0xC0) == 0x80: # byte di continuazione UTF-8 - cut -= 1 - parts.append(encoded[:cut].decode("utf-8")) - encoded = encoded[cut:] - parts.append(encoded.decode("utf-8")) - return "\r\n ".join(parts) + w = self.widgets["baltx_fees"] + if w not in bal_window.txfee_widgets: + bal_window.txfee_widgets.append(w) + box.addWidget(self.widgets["locktime"]) + box.addWidget(self.widgets["threshold"]) + box.addWidget(self.calendar_button) + box.addWidget(self.widgets["baltx_fees"]) + def create_alarms(self, alarm_start, alarm_end): + days = (alarm_end - alarm_start).days+1 + lines = [] + for i in range(1, days): + lines.extend( + [ + "BEGIN:VALARM", + f"TRIGGER;RELATED=END:-P{i}D", + "ACTION:DISPLAY", + # f"DESCRIPTION:{self.bal_window.bal_plugin.ALARM_DESCRIPTION.get()}", + "END:VALARM", + ] + ) + return lines def open_or_save_calendar(self): - now = self.format_time(datetime.now()) + now = BalCalendar.format_time(datetime.now()) - alarm_start = self.format_time(self.widgets['threshold'].alarm) - alarm_end = self.format_time(self.widgets['locktime'].alarm) + locktime = self.widgets["locktime"].alarm + threshold = self.widgets["threshold"].alarm + alarm_end = BalCalendar.format_time(locktime) + alarm_start = BalCalendar.format_time(threshold) + days_difference = (locktime - threshold).days - heirs_details = "\n".join(f"{heir} - {self.bal_window.heirs[heir][1]}" for heir in self.bal_window.heirs) - event_description = self.ical_escape(f"Your will for wallet {self.bal_window.wallet} is going to expire\n{heirs_details}") - uid = f"{int(datetime.now().timestamp())}-{os.getpid()}@local" # UID univoco semplice - summary = self.ical_escape(f"Bitcoin After Life {self.bal_window.wallet} expiry") + heirs_details = "\n".join(f"{heir} - {self.bal_window.heirs[heir][0]}, {self.bal_window.heirs[heir][1]}" for heir in self.bal_window.heirs) + event_description = BalCalendar.ical_escape( + f"{self.bal_window.bal_plugin.EVENT_DESCRIPTION.get()}".replace("$wallet_name",str(self.bal_window.wallet)).replace("$heirs_complete",heirs_details) + ) + event_description =f"{event_description}{heirs_details}" + uid = f"bal-{str(self.bal_window.wallet)}" + summary = BalCalendar.ical_escape( + f"{self.bal_window.bal_plugin.EVENT_SUMMARY.get()}".replace("$wallet_name",str(self.bal_window.wallet)) + ) lines = [ "BEGIN:VCALENDAR", "VERSION:2.0", - "PRODID:-//Example//EN", + f"PRODID:-//Bitcoin After Life//Electrum Plugin/{BalPlugin.version}", "BEGIN:VEVENT", f"UID:{uid}", f"DTSTAMP:{now}", - f"DTSTART:{alarm_start}", + f"DTSTART:{alarm_end}", f"DTEND:{alarm_end}", f"SUMMARY:{summary}", f"DESCRIPTION:{event_description}", + ] + lines.extend(self.create_alarms(threshold, locktime)) + lines.extend([ "END:VEVENT", "END:VCALENDAR", - ] + ]) ics_content = "\r\n".join(lines) + "\r\n" - temp_path = self.write_temp_ics(ics_content) - opened = self.open_with_default_app(temp_path) + temp_path = BalCalendar.write_temp_ics(ics_content) + opened = BalCalendar.open_with_default_app( + self.bal_window.bal_plugin.CALENDAR_APP.get(), temp_path + ) if opened: _logger.info(f"File opened with default app: {temp_path}") else: - export_meta_gui(self.bal_window.window, f"{self.base_field}_event.csv", self.save_to_cwd) + export_meta_gui( + self.bal_window.window, f"{self.base_field}_event.csv", self.save_to_cwd + ) def on_locktime_change(self): - threshold_value = str(self.widgets['threshold'].get_value()) - locktime_value = str(self.widgets['locktime'].get_value()) + locktime = self.widgets["locktime"].get_value() + threshold = self.widgets["threshold"].get_value() + locktime = BalTimestamp(locktime) + threshold = BalTimestamp(threshold) - dt=None - if threshold_value[-1] in ['d','y']: - ts=int(threshold_value[:-1]) - min_locktime = min(Will.get_min_locktime(self.bal_window.willitems,NLOCKTIME_MAX),Util.parse_locktime_string(locktime_value)) - threshold_value = ts*365 if threshold_value[-1] == 'y' else ts - dt = datetime.fromtimestamp(min_locktime) - dt = dt - timedelta(days = threshold_value) - self.widgets['threshold'].editor.label.setText(dt.strftime("%Y-%m-%d")) - self.widgets['threshold'].alarm = dt - else: - self.widgets['threshold'].alarm = datetime.fromtimestamp(Util.parse_locktime_string(self.widgets['threshold'].get_value())) + min_locktime = min( + Will.get_min_locktime(self.bal_window.willitems, NLOCKTIME_MAX), + locktime.to_timestamp(), + ) + td = threshold.to_date(min_locktime, True) + self.widgets["threshold"].alarm=td + self.bal_window.will_settings["real_threshold"]=td.timestamp() + try: + self.widgets["threshold"].editor.label.setText(td.strftime("%Y-%m-%d")) + except Exception as _e: + pass - if locktime_value[-1] in ['d','y']: - lt = int(locktime_value[:-1]) - locktime_value = lt*365 if locktime_value[-1] == 'y' else lt - dt =datetime.now() - dt += timedelta(days= locktime_value) - dt=dt.replace(hour=0,minute=0,second=0,microsecond=0) - self.widgets['locktime'].editor.label.setText(dt.strftime("%Y-%m-%d")) - self.widgets['locktime'].alarm = dt - else: - self.widgets['locktime'].alarm=datetime.fromtimestamp(Util.parse_locktime_string(self.widgets['locktime'].get_value())) + td = locktime.to_date() + alarm = BalTimestamp(min_locktime).to_date() + self.widgets["locktime"].alarm=alarm + self.bal_window.will_settings["real_locktime"]=td.timestamp() + try: + self.widgets["locktime"].editor.label.setText(td.strftime("%Y-%m-%d")) + except Exception as _e: + pass class PercAmountEdit(BTCAmountEdit): - def __init__( - self, decimal_point, is_int=False, parent=None, *, max_amount=None - ): + def __init__(self, decimal_point, is_int=False, parent=None, *, max_amount=None): super().__init__(decimal_point, is_int, parent, max_amount=max_amount) def numbify(self): @@ -1776,12 +1861,14 @@ class BalDialog(WindowModalDialog): WindowModalDialog.__init__(self, parent, title) # WindowModalDialog.__init__(self,parent) self.setWindowIcon(read_QIcon_from_bytes(bal_plugin.read_file(icon))) - def closeEvent(self,event): - self._stopping=True + + def closeEvent(self, event): + self._stopping = True if self.thread: self.thread.stop() - def hideEvent(self,event): - self._stopping=True + + def hideEvent(self, event): + self._stopping = True if self.thread: self.thread.stop() @@ -1871,9 +1958,7 @@ class BalWizardDialog(BalDialog): def closeEvent(self, event): self._stopping = True - self.thread.stop() - - #self.bal_window.heir_list_widget.will_settings_widget.update_will_settings() + # self.bal_window.heir_list_widget.will_settings_widget.update_will_settings() pass @@ -2010,7 +2095,7 @@ class BalWizardWEDownloadWidget(BalWizardWidget): if index == 2: - def doNothing(): + def do_nothing(): self.bal_window.willexecutors.update(self.willexecutors) Willexecutors.save( self.bal_window.bal_plugin, self.bal_window.willexecutors @@ -2021,7 +2106,7 @@ class BalWizardWEDownloadWidget(BalWizardWidget): self.bal_window.window, _("willexecutors"), self.import_json_file, - doNothing, + do_nothing, ) if index < 2: @@ -2029,8 +2114,10 @@ class BalWizardWEDownloadWidget(BalWizardWidget): def on_success(willexecutors): def ping_on_success(result): ping_on_done() + def ping_on_failure(exec_info): ping_on_done() + def ping_on_done(): if index < 1: for we in self.bal_window.willexecutors: @@ -2041,10 +2128,10 @@ class BalWizardWEDownloadWidget(BalWizardWidget): ) self.bal_window.ping_willexecutors( - self.bal_window.willexecutors,ping_on_success,ping_on_failure + self.bal_window.willexecutors, ping_on_success, ping_on_failure ) - self.bal_window.download_list(self.bal_window.willexecutors,on_success) + self.bal_window.download_list(self.bal_window.willexecutors, on_success) elif index == 3: # TODO DO NOTHING @@ -2087,10 +2174,7 @@ class BalWizardLocktimeAndFeeWidget(BalWizardWidget): widget = QWidget() layout = QVBoxLayout(widget) - layout.addWidget(LockTimeWidget(self.bal_window,widget)) - layout.addWidget(ThresholdTimeWidget(self.bal_window,widget)) - layout.addWidget(BalTxFeesWidget(self.bal_window,widget)) - + layout.addWidget(WillSettingsWidget(self.bal_window, self, "v")) spacer_widget = QWidget() spacer_widget.setSizePolicy( QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding @@ -2184,8 +2268,23 @@ class BalBlockingWaitingDialog(BalDialog): # close popup self.accept() +class BalLineEdit(QLineEdit): + def __init__(self,variable): + QLineEdit.__init__(self) + self.setText(variable.get()) + def on_edit(): + variable.set(self.text()) + self.editingFinished.connect(on_edit) -class bal_checkbox(QCheckBox): +class BalTextEdit(QTextEdit): + def __init__(self,variable): + QTextEdit.__init__(self) + self.setPlainText(variable.get()) + def on_edit(): + variable.set(self.toPlainText()) + self.textChanged.connect(on_edit) + +class BalCheckBox(QCheckBox): def __init__(self, variable, on_click=None): QCheckBox.__init__(self) self.setChecked(variable.get()) @@ -2193,7 +2292,7 @@ class bal_checkbox(QCheckBox): def on_check(v): variable.set(v == 2) - variable.get() + #variable.get() if self.on_click: self.on_click() @@ -2216,10 +2315,10 @@ class BalBuildWillDialog(BalDialog): self.bal_plugin = bal_window.bal_plugin self.message_label = QLabel(_("Building Will:")) self.vbox = QVBoxLayout(self) - self.vbox.addWidget(self.message_label,0) + self.vbox.addWidget(self.message_label, 0) self.qwidget = QWidget(self) - self.vbox.addWidget(self.qwidget,1) - self.labelsbox=QVBoxLayout(self.qwidget) + self.vbox.addWidget(self.qwidget, 1) + self.labelsbox = QVBoxLayout(self.qwidget) self.setMinimumWidth(600) self.setMinimumHeight(100) self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred) @@ -2249,24 +2348,29 @@ class BalBuildWillDialog(BalDialog): self.exec() def task_phase1(self): - txs=None + txs = None _logger.debug("close plugin phase 1 started") varrow = self.msg_set_status("checking variables") try: self.bal_window.init_class_variables() - except CheckAliveException as cae: + except CheckAliveError as cae: fee_per_byte = self.bal_window.will_settings.get("baltx_fees", 1) tx = Will.invalidate_will( self.bal_window.willitems, self.bal_window.wallet, fee_per_byte ) if tx: - _logger.debug("during phase1 CAE: {}, Continue to invalidate".format(cae)) - self.msg_set_checking(self.msg_warning("Check Alive Threshold Passed: you have to Invalidate your old Will")) + _logger.debug( + "during phase1 CAE: {}, Continue to invalidate".format(cae) + ) + self.msg_set_checking( + self.msg_warning( + "Check Alive Threshold Passed: you have to Invalidate your old Will" + ) + ) else: raise cae return None, tx - except Exception as e: raise e try: @@ -2336,7 +2440,7 @@ class BalBuildWillDialog(BalDialog): _("Skipped"), self.COLOR_ERROR, ) - return False,None + return False, None self.bal_window.check_will() for wid in Will.only_valid(self.bal_window.willitems): @@ -2446,7 +2550,9 @@ class BalBuildWillDialog(BalDialog): self.bal_plugin = self.bal_window.bal_plugin w = self.bal_window.willitems[wid] - w.set_check_willexecutor(Willexecutors.check_transaction(wid, w.we["url"])) + w.set_check_willexecutor( + Willexecutors.check_transaction(wid, w.we["url"]) + ) row = self.msg_edit_row( "checked {} - {} : {}".format( self.bal_window.willitems[wid].we["url"], @@ -2466,7 +2572,8 @@ class BalBuildWillDialog(BalDialog): self.msg_set_pushing(self.msg_error(e)) self.wait(10) if not self._stopping: - self.loop_push() + pass + # self.loop_push() def invalidate_task(self, password, bal_window, tx): _logger.debug(f"invalidate tx: {tx}") @@ -2484,7 +2591,7 @@ class BalBuildWillDialog(BalDialog): except Exception as e: (f"exception:{e}") self.msg_set_invalidating(f"Error: {e}") - raise Exception("Impossible to sign") + raise Exception("Impossible to sign") from e def on_success_invalidate(self, success): self.thread.add( @@ -2496,7 +2603,7 @@ class BalBuildWillDialog(BalDialog): def on_success_phase1(self, result): self.have_to_sign, tx = list(result) - #if not tx: + # if not tx: # self.msg_edit_row(self.msg_error("Error, no tx was built")) # return _logger.debug("have to sign {}".format(self.have_to_sign)) @@ -2574,7 +2681,7 @@ class BalBuildWillDialog(BalDialog): self.msg_set_pushing(self.msg_ok()) except Exception as e: - #td = traceback.format_exc() + # td = traceback.format_exc() self.msg_set_pushing(self.msg_error(e)) self.msg_edit_row(self.msg_ok()) self.wait(5) @@ -2585,13 +2692,13 @@ class BalBuildWillDialog(BalDialog): def on_error_phase1(self, error): self.bal_window.update_all() - a,b,c = error + a, b, c = error self.msg_edit_row(self.msg_error(f"Error: {b}")) _logger.error(f"error phase1: {b}") def on_error_phase2(self, error): self.bal_window.upade_all() - a,b,c = error + a, b, c = error self.msg_edit_row(self.msg_error(f"Error: {b}")) _logger.error(f"error phase2: {b}") @@ -2649,7 +2756,6 @@ class BalBuildWillDialog(BalDialog): self.labels.append(line) row = len(self.labels) - 1 - self.updatemessage.emit() return row @@ -2661,7 +2767,7 @@ class BalBuildWillDialog(BalDialog): pass self.updatemessage.emit() - #def clear_layout(self,layout): + # def clear_layout(self,layout): # while layout.count(): # item = layout.takeAt(0) # w = item.widget() @@ -2669,7 +2775,7 @@ class BalBuildWillDialog(BalDialog): # w.setParent(None) # w.deleteLater() - #def msg_update(self): + # def msg_update(self): # self.clear_layout(self.labelsbox) # for label in self.labels: # label=label.replace("\n","
") @@ -2688,10 +2794,9 @@ class BalBuildWillDialog(BalDialog): full_text = "

".join(self.labels).replace("\n", "
") self.message_label.setText(full_text) self.message_label.adjustSize() - #self.setMinimumHeight(len(self.labels)*40) + # self.setMinimumHeight(len(self.labels)*40) self.resize(self.sizeHint()) - def get_text(self): return self.message_label.text() @@ -2718,8 +2823,10 @@ class HeirListWidget(MyTreeView, MessageBoxMixin): def createEditor(self, parent, option, index): return QLineEdit(parent) + def setEditorData(self, editor, index): editor.setText(index.data()) + def setModelData(self, editor, model, index): model.setData(index, editor.text()) @@ -2737,7 +2844,6 @@ class HeirListWidget(MyTreeView, MessageBoxMixin): self.decimal_point = bal_window.window.get_decimal_point() self.bal_window = bal_window - try: self.setModel(QStandardItemModel(self)) self.sortByColumn(self.Columns.NAME, Qt.SortOrder.AscendingOrder) @@ -2857,10 +2963,13 @@ class HeirListWidget(MyTreeView, MessageBoxMixin): if key == current_key: idx = self.model().index(row_count, self.Columns.NAME) set_current = QPersistentModelIndex(idx) + try: + self.will_settings_widget.on_locktime_change() + except Exception as e: + pass self.set_current_idx(set_current) # FIXME refresh loses sort order; so set "default" here: self.filter() - run_hook("update_heirs_tab", self) def refresh_row(self, key, row): # nothing to update here @@ -2876,13 +2985,14 @@ class HeirListWidget(MyTreeView, MessageBoxMixin): menu.addAction(_("Import"), self.bal_window.import_heirs) menu.addAction(_("Export"), lambda: self.bal_window.export_heirs()) - widget = QWidget() - layout = QHBoxLayout(widget) - self.will_settings_widget=WillSettingsWidget(self.bal_window,widget) - - layout.addWidget(self.will_settings_widget) newHeirButton = QPushButton(_("New Heir")) newHeirButton.clicked.connect(self.bal_window.new_heir_dialog) + + widget = QWidget(self) + layout = QHBoxLayout(widget) + self.will_settings_widget = WillSettingsWidget(self.bal_window, self) + + layout.addWidget(self.will_settings_widget) layout.addWidget(newHeirButton) toolbar.insertWidget(2, widget) @@ -2894,7 +3004,7 @@ class HeirListWidget(MyTreeView, MessageBoxMixin): self.bal_window.prepare_will() -class PreviewList(MyTreeView): +class PreviewList(MyTreeView, MessageBoxMixin): class Columns(MyTreeView.BaseColumnsEnum): LOCKTIME = enum.auto() TXID = enum.auto() @@ -2911,29 +3021,39 @@ class PreviewList(MyTreeView): ROLE_HEIR_KEY = Qt.ItemDataRole.UserRole + 2000 key_role = ROLE_HEIR_KEY + def createEditor(self, parent, option, index): + return QLineEdit(parent) + + def setEditorData(self, editor, index): + editor.setText(index.data()) + + def setModelData(self, editor, model, index): + model.setData(index, editor.text()) + def __init__(self, bal_window: "BalWindow", parent, will): super().__init__( parent=parent, + main_window=bal_window.window, stretch_column=self.Columns.TXID, ) - self.parent = parent + # self.parent = parent self.bal_window = bal_window self.decimal_point = bal_window.window.get_decimal_point - self.setModel(QStandardItemModel(self)) - self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection) if will is not None: self.will = will else: self.will = bal_window.willitems - self.wallet = bal_window.window.wallet - self.setModel(QStandardItemModel(self)) - self.sortByColumn(self.Columns.LOCKTIME, Qt.SortOrder.AscendingOrder) + try: + self.setModel(QStandardItemModel(self)) + self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection) + self.sortByColumn(self.Columns.NAME, Qt.SortOrder.AscendingOrder) + except Exception as e: + pass + self.setSortingEnabled(True) self.std_model = self.model() - self.config = bal_window.bal_plugin.config - self.bal_plugin = self.bal_window.bal_plugin self.update() @@ -2968,7 +3088,7 @@ class PreviewList(MyTreeView): _("check ").format(column_title), lambda: self.check_transactions(selected_keys), ) - if self.bal_plugin.ENABLE_MULTIVERSE.get(): + if self.bal_window.bal_plugin.ENABLE_MULTIVERSE.get(): try: self.importaction = self.menu.addAction( _("Import"), self.import_will @@ -3037,7 +3157,7 @@ class PreviewList(MyTreeView): tx = bal_tx.tx labels = [""] * len(self.Columns) - labels[self.Columns.LOCKTIME] = Util.locktime_to_str(tx.locktime) + labels[self.Columns.LOCKTIME] = str(BalTimestamp(tx.locktime)) labels[self.Columns.TXID] = txid we = "None" if bal_tx.we: @@ -3090,6 +3210,10 @@ class PreviewList(MyTreeView): set_current = tmp self.sortByColumn(self.Columns.LOCKTIME, Qt.SortOrder.AscendingOrder) self.setSortingEnabled(True) + try: + self.will_settings_widget.on_locktime_change() + except Exception as _e: + pass def create_toolbar(self, config): toolbar, menu = self.create_toolbar_with_menu("") @@ -3097,7 +3221,7 @@ class PreviewList(MyTreeView): menu.addAction(_("Display"), self.bal_window.preview_modal_dialog) menu.addAction(_("Sign"), self.ask_password_and_sign_transactions) menu.addAction(_("Export"), self.export_will) - if self.bal_plugin.ENABLE_MULTIVERSE.get(): + if self.bal_window.bal_plugin.ENABLE_MULTIVERSE.get(): self.importaction = menu.addAction(_("Import"), self.import_will) menu.addAction(_("Broadcast"), self.broadcast) menu.addAction(_("Check"), self.check) @@ -3105,16 +3229,15 @@ class PreviewList(MyTreeView): wizard = QPushButton(_("Setup Wizard")) wizard.clicked.connect(self.bal_window.init_wizard) - #display = QPushButton(_("Display")) - #display.clicked.connect(self.bal_window.preview_modal_dialog) + # display = QPushButton(_("Display")) + # display.clicked.connect(self.bal_window.preview_modal_dialog) refresh = QPushButton(_("Refresh")) refresh.clicked.connect(self.check) - widget = QWidget() + widget = QWidget(self) hlayout = QHBoxLayout(widget) - self.will_settings_widget=WillSettingsWidget(self.bal_window,widget) - + self.will_settings_widget = WillSettingsWidget(self.bal_window, self) hlayout.addWidget(self.will_settings_widget) hlayout.addWidget(wizard) hlayout.addWidget(refresh) @@ -3175,95 +3298,94 @@ class PreviewList(MyTreeView): self.update() -class PreviewDialog(BalDialog, MessageBoxMixin): - def __init__(self, bal_window, will): - self.parent = bal_window.window - BalDialog.__init__( - self, bal_window=bal_window, bal_plugin=bal_window.bal_plugin - ) - self.bal_plugin = bal_window.bal_plugin - self.gui_object = self.bal_plugin.gui_object - self.config = self.bal_plugin.config - self.bal_window = bal_window - self.wallet = bal_window.window.wallet - self.format_amount = bal_window.window.format_amount - self.base_unit = bal_window.window.base_unit - self.format_fiat_and_units = bal_window.window.format_fiat_and_units - self.fx = bal_window.window.fx - self.format_fee_rate = bal_window.window.format_fee_rate - self.show_address = bal_window.window.show_address - if not will: - self.will = bal_window.willitems - else: - self.will = will - self.setWindowTitle(_("Transactions Preview")) - self.setMinimumSize(1000, 200) - self.size_label = QLabel() - self.transactions_list = PreviewList(self.bal_window, self.will) - - try: - self.bal_window.init_class_variables() - except Exception as e: - _logger.error(f"PreviewDialog Exception: {e}") - self.check_will() - - vbox = QVBoxLayout(self) - vbox.addWidget(self.size_label) - vbox.addWidget(self.transactions_list) - buttonbox = QHBoxLayout() - - b = QPushButton(_("Sign")) - b.clicked.connect(self.transactions_list.ask_password_and_sign_transactions) - buttonbox.addWidget(b) - - b = QPushButton(_("Export Will")) - b.clicked.connect(self.transactions_list.export_will) - buttonbox.addWidget(b) - - b = QPushButton(_("Broadcast")) - b.clicked.connect(self.transactions_list.broadcast) - buttonbox.addWidget(b) - - b = QPushButton(_("Invalidate will")) - b.clicked.connect(self.transactions_list.invalidate_will) - buttonbox.addWidget(b) - - vbox.addLayout(buttonbox) - - self.update() - - def update_will(self, will): - self.will.update(will) - self.transactions_list.update_will(will) - self.update() - - def update(self): - self.transactions_list.update() - - def is_hidden(self): - return self.isMinimized() or self.isHidden() - - def show_or_hide(self): - if self.is_hidden(): - self.bring_to_top() - else: - self.hide() - - def bring_to_top(self): - self.show() - self.raise_() - - def closeEvent(self, event): - event.accept() +# class PreviewDialog(BalDialog, MessageBoxMixin): +# def __init__(self, bal_window, will): +# self.parent = bal_window.window +# BalDialog.__init__( +# self, bal_window=bal_window, bal_plugin=bal_window.bal_plugin +# ) +# self.bal_plugin = bal_window.bal_plugin +# self.gui_object = self.bal_plugin.gui_object +# self.config = self.bal_plugin.config +# self.bal_window = bal_window +# self.wallet = bal_window.window.wallet +# self.format_amount = bal_window.window.format_amount +# self.base_unit = bal_window.window.base_unit +# self.format_fiat_and_units = bal_window.window.format_fiat_and_units +# self.fx = bal_window.window.fx +# self.format_fee_rate = bal_window.window.format_fee_rate +# self.show_address = bal_window.window.show_address +# if not will: +# self.will = bal_window.willitems +# else: +# self.will = will +# self.setWindowTitle(_("Transactions Preview")) +# self.setMinimumSize(1000, 200) +# self.size_label = QLabel() +# self.transactions_list = PreviewList(self.bal_window,self, self.will) +# +# try: +# self.bal_window.init_class_variables() +# except Exception as e: +# _logger.error(f"PreviewDialog Exception: {e}") +# self.check_will() +# +# vbox = QVBoxLayout(self) +# vbox.addWidget(self.size_label) +# vbox.addWidget(self.transactions_list) +# buttonbox = QHBoxLayout() +# +# b = QPushButton(_("Sign")) +# b.clicked.connect(self.transactions_list.ask_password_and_sign_transactions) +# buttonbox.addWidget(b) +# +# b = QPushButton(_("Export Will")) +# b.clicked.connect(self.transactions_list.export_will) +# buttonbox.addWidget(b) +# +# b = QPushButton(_("Broadcast")) +# b.clicked.connect(self.transactions_list.broadcast) +# buttonbox.addWidget(b) +# +# b = QPushButton(_("Invalidate will")) +# b.clicked.connect(self.transactions_list.invalidate_will) +# buttonbox.addWidget(b) +# +# vbox.addLayout(buttonbox) +# +# self.update() +# +# def update_will(self, will): +# self.will.update(will) +# self.transactions_list.update_will(will) +# self.update() +# +# def update(self): +# self.transactions_list.update() +# +# def is_hidden(self): +# return self.isMinimized() or self.isHidden() +# +# def show_or_hide(self): +# if self.is_hidden(): +# self.bring_to_top() +# else: +# self.hide() +# +# def bring_to_top(self): +# self.show() +# self.raise_() +# +# def closeEvent(self, event): +# event.accept() class WillDetailDialog(BalDialog): def __init__(self, bal_window): self.will = bal_window.willitems - self.threshold = Util.parse_locktime_string( - bal_window.will_settings["threshold"] - ) + self.threshold = bal_window.will_settings["real_threshold"] + self.bal_window = bal_window Will.add_willtree(self.will) super().__init__(bal_window.window, bal_window.bal_plugin) @@ -3300,7 +3422,7 @@ class WillDetailDialog(BalDialog): self.paint_scroll_area() self.vlayout.addWidget( - QLabel(_("Expiration date: ") + Util.locktime_to_str(self.threshold)) + QLabel(_("Expiration date: ") + str(BalTimestamp(self.threshold))) ) self.vlayout.addWidget(self.scrollbox) w = QWidget() @@ -3393,8 +3515,8 @@ class WillWidget(QWidget): partial(self.parent.bal_window.show_transaction, txid=w) ) detaillayout.addWidget(willpushbutton) - locktime = Util.locktime_to_str(self.will[w].tx.locktime) - creation = Util.locktime_to_str(self.will[w].time) + locktime = str(BalTimestamp(self.will[w].tx.locktime)) + creation = str(BalTimestamp(self.will[w].time)) def qlabel(title, value): label = "" + _(str(title)) + f":\t{str(value)}" @@ -3676,11 +3798,6 @@ class WillExecutorListWidget(MyTreeView): set_current = QPersistentModelIndex(idx) self.set_current_idx(set_current) self.filter() - #try: - # self.parent.save_willexecutors() - #except Exception as e: - # print("exception saving willexecutors",e) - except Exception as e: _logger.error(f"error updating willexcutor {e}") raise e @@ -3707,7 +3824,7 @@ class WillExecutorWidget(QWidget, MessageBoxMixin): widget = QWidget() hbox = QHBoxLayout(widget) hbox.addWidget(QLabel(_("Add transactions without willexecutor"))) - heir_no_willexecutor = bal_checkbox(self.bal_plugin.NO_WILLEXECUTOR) + heir_no_willexecutor = BalCheckBox(self.bal_plugin.NO_WILLEXECUTOR) hbox.addWidget(heir_no_willexecutor) spacer_widget = QWidget() spacer_widget.setSizePolicy( @@ -3750,10 +3867,10 @@ class WillExecutorWidget(QWidget, MessageBoxMixin): } self.will_executor_list_widget.update() - def download_list(self,wes=None): + def download_list(self, wes=None): if not wes: - wes=self.willexecutors_list - self.bal_window.download_list(wes,self.save_willexecutors) + wes = self.willexecutors_list + self.bal_window.download_list(wes, self.save_willexecutors) def export_file(self, path): export_meta_gui( @@ -3773,8 +3890,8 @@ class WillExecutorWidget(QWidget, MessageBoxMixin): def update_willexecutors(self, wes=None): if not wes: - wes=self.willexecutors_list - self.bal_window.ping_willexecutors(wes,self.save_willexecutors) + wes = self.willexecutors_list + self.bal_window.ping_willexecutors(wes, self.save_willexecutors) def import_json_file(self, path): data = read_json_file(path) @@ -3786,9 +3903,9 @@ class WillExecutorWidget(QWidget, MessageBoxMixin): def _validate(self, data): return data - def save_willexecutors(self,wes=None): + def save_willexecutors(self, wes=None): if not wes: - wes=self.willexecutors_list + wes = self.willexecutors_list self.willexecutors_list.update(wes) self.will_executor_list_widget.update() Willexecutors.save(self.bal_window.bal_plugin, self.willexecutors_list) @@ -3830,36 +3947,32 @@ class WillExecutorDialog(BalDialog, MessageBoxMixin): event.accept() -class CheckAliveException(Exception): - def __init__(self,timestamp_to_check): - self.timestamp_to_check = timestamp_to_check - def __str__(self): - return "Check alive expired please update it: {}".format(datetime.fromtimestamp(self.timestamp_to_check).isoformat()) +class CheckAliveError(Exception): + def __init__(self, timestamp_to_check): + self.timestamp_to_check = timestamp_to_check -def log_error(exec_info,window=None): + def __str__(self): + return "Check alive expired please update it: {}".format( + datetime.fromtimestamp(self.timestamp_to_check).isoformat() + ) + + +def log_error(exec_info, window=None): _logger.error(exec_info) - tb=traceback.format_exc() - while tb is not None: - try: - frame = tb.tb_frame - _logger.error("file:", frame.f_code.co_filename) - _logger.error("name:", frame.f_code.co_name) - _logger.error("line:", tb.tb_lineno) - _logger.error("lasti:", tb.tb_lasti) - tb = tb.tb_next - except Exception as e: - _logger.error(tb,e) - tb= None - break + tb = traceback.format_exc() + _logger.error(tb) if window is not None: window.show_error(exec_info) + def export_meta_gui(electrum_window, title, exporter): filter_ = "All files (*)" filename = getSaveFileName( parent=electrum_window, title=_("Select file to save your {}".format(title)), - filename="BALplugin_{}_{}_{}".format(BalPlugin.chainname,str(electrum_window.wallet),title), + filename="BALplugin_{}_{}_{}".format( + BalPlugin.chainname, str(electrum_window.wallet), title + ), filter=filter_, config=electrum_window.config, ) @@ -3874,3 +3987,58 @@ def export_meta_gui(electrum_window, title, exporter): _("Your {0} were exported to '{1}'".format(title, str(filename))) ) + +class BalCalendar: + @staticmethod + def write_temp_ics(content): + fd, path = tempfile.mkstemp(prefix="event_", suffix=".ics") + with os.fdopen(fd, "w", encoding="utf-8") as f: + f.write(content) + return path + + @staticmethod + def open_with_default_app(calendar_app, path): + try: + subprocess.check_call([calendar_app, path]) + return True + except Exception as _e: + return False + + @staticmethod + def save_to_cwd(path, filename="evento.ics"): + target = os.path.abspath(filename) + # se il file esiste, sovrascrive + with open(path, "rb") as src, open(target, "wb") as dst: + dst.write(src.read()) + return target + + @staticmethod + def format_time(time): + return time.astimezone(timezone.utc).strftime("%Y%m%dT%H%M%SZ") + #return time.astimezone(timezone.utc).strftime("%Y%m%d") + + @staticmethod + def ical_escape(text: str) -> str: + # escape per RFC5545: backslash, ; , newlines + text = ( + text.replace("\\", "\\\\") + .replace(";", r"\;") + .replace(",", r"\,") + .replace("\n", r"\n") + ) + return BalCalendar.fold_ical_line(text) + + @staticmethod + def fold_ical_line(line: str, limit: int = 75) -> str: + # ritorna linee separate da CRLF e folding con spazio iniziale sulle righe successive + encoded = line.encode("utf-8") + parts = [] + while len(encoded) > limit: + # taglia senza spezzare byte UTF-8 + cut = limit + while (encoded[cut] & 0xC0) == 0x80: # byte di continuazione UTF-8 + cut -= 1 + parts.append(encoded[:cut].decode("utf-8")) + encoded = encoded[cut:] + parts.append(encoded.decode("utf-8")) + return "\r\n ".join(parts) diff --git a/will.py b/will.py index eccd8f9..1bcc84a 100644 --- a/will.py +++ b/will.py @@ -526,7 +526,7 @@ class Will: @staticmethod def get_min_locktime(will,default_value=None): - return min((v.tx.locktime for v in will.values() if v.get_status('VALID')), default=default_value) + return min((v.tx.locktime for v in will.values() if v.get_status('VALID')), default=default_value)