|
@@ -14,12 +14,39 @@ proto.xmr.tw.view: Monero protocol base class for tracking wallet view classes
|
|
|
|
|
|
|
|
from collections import namedtuple
|
|
from collections import namedtuple
|
|
|
|
|
|
|
|
-from ....xmrwallet import op as xmrwallet_op
|
|
|
|
|
|
|
+from ....obj import ImmutableAttr
|
|
|
|
|
+from ....color import red, green
|
|
|
|
|
+from ....addr import MoneroIdx
|
|
|
|
|
+from ....amt import CoinAmtChk
|
|
|
from ....seed import SeedID
|
|
from ....seed import SeedID
|
|
|
|
|
+from ....xmrwallet import op as xmrwallet_op
|
|
|
from ....tw.view import TwView
|
|
from ....tw.view import TwView
|
|
|
|
|
+from ....tw.unspent import TwUnspentOutputs
|
|
|
|
|
|
|
|
class MoneroTwView:
|
|
class MoneroTwView:
|
|
|
|
|
|
|
|
|
|
+ item_desc = 'account'
|
|
|
|
|
+ nice_addr_w = {'addr': 20}
|
|
|
|
|
+ total = None
|
|
|
|
|
+
|
|
|
|
|
+ sort_disp = {
|
|
|
|
|
+ 'addr': 'Addr',
|
|
|
|
|
+ 'age': 'Age',
|
|
|
|
|
+ 'amt': 'Amt',
|
|
|
|
|
+ 'twmmid': 'MMGenID'}
|
|
|
|
|
+
|
|
|
|
|
+ # NB: For account-based views, ALL sort keys MUST begin with acct_sort_key!
|
|
|
|
|
+ sort_funcs = {
|
|
|
|
|
+ 'addr': lambda i: '{}:{}'.format(i.twmmid.obj.acct_sort_key, i.addr),
|
|
|
|
|
+ 'age': lambda i: '{}:{:020}'.format(i.twmmid.obj.acct_sort_key, 0 - i.confs),
|
|
|
|
|
+ 'amt': lambda i: '{}:{:050}'.format(i.twmmid.obj.acct_sort_key, i.amt.to_unit('atomic')),
|
|
|
|
|
+ 'twmmid': lambda i: i.twmmid.sort_key} # sort_key begins with acct_sort_key
|
|
|
|
|
+
|
|
|
|
|
+ class MoneroTwViewItem(TwUnspentOutputs.MMGenTwUnspentOutput):
|
|
|
|
|
+ valid_attrs = {'amt', 'unlocked_amt', 'comment', 'twmmid', 'addr', 'confs', 'is_used', 'skip'}
|
|
|
|
|
+ unlocked_amt = ImmutableAttr(CoinAmtChk, include_proto=True)
|
|
|
|
|
+ is_used = ImmutableAttr(bool)
|
|
|
|
|
+
|
|
|
class rpc:
|
|
class rpc:
|
|
|
caps = ()
|
|
caps = ()
|
|
|
is_remote = False
|
|
is_remote = False
|
|
@@ -80,6 +107,117 @@ class MoneroTwView:
|
|
|
|
|
|
|
|
return dict(gen_addrs())
|
|
return dict(gen_addrs())
|
|
|
|
|
|
|
|
|
|
+ def gen_data(self, rpc_data, lbl_id):
|
|
|
|
|
+ return (
|
|
|
|
|
+ self.MoneroTwViewItem(
|
|
|
|
|
+ self.proto,
|
|
|
|
|
+ twmmid = twmmid,
|
|
|
|
|
+ addr = data['addr'],
|
|
|
|
|
+ confs = data['confs'],
|
|
|
|
|
+ is_used = data['is_used'],
|
|
|
|
|
+ comment = data['lbl'].comment,
|
|
|
|
|
+ amt = data['amt'],
|
|
|
|
|
+ unlocked_amt = data['unlocked_amt'])
|
|
|
|
|
+ for twmmid, data in rpc_data.items())
|
|
|
|
|
+
|
|
|
|
|
+ def get_disp_data(self, input_data=None):
|
|
|
|
|
+ data = self.data if input_data is None else input_data
|
|
|
|
|
+ chk_fail_msg = 'For account-based views, ALL sort keys MUST begin with acct_sort_key!'
|
|
|
|
|
+ ad = namedtuple('accts_data', ['idx', 'acct_idx', 'total', 'unlocked_total', 'data'])
|
|
|
|
|
+ bd = namedtuple('accts_data_data', ['disp_data_idx', 'data'])
|
|
|
|
|
+ def gen_accts_data():
|
|
|
|
|
+ idx, acct_idx = (None, None)
|
|
|
|
|
+ total, unlocked_total, d_acc = (0, 0, {})
|
|
|
|
|
+ chk_acc = [] # check for out-of-order accounts (developer idiot-proofing)
|
|
|
|
|
+ for n, d in enumerate(data):
|
|
|
|
|
+ m = d.twmmid.obj
|
|
|
|
|
+ if idx != m.idx or acct_idx != m.acct_idx:
|
|
|
|
|
+ if idx:
|
|
|
|
|
+ yield ad(idx, acct_idx, total, unlocked_total, d_acc)
|
|
|
|
|
+ idx = m.idx
|
|
|
|
|
+ acct_idx = m.acct_idx
|
|
|
|
|
+ total = d.amt
|
|
|
|
|
+ unlocked_total = d.unlocked_amt
|
|
|
|
|
+ d_acc = {m.addr_idx: bd(n, d)}
|
|
|
|
|
+ chk_acc.append((idx, acct_idx))
|
|
|
|
|
+ else:
|
|
|
|
|
+ total += d.amt
|
|
|
|
|
+ unlocked_total += d.unlocked_amt
|
|
|
|
|
+ d_acc[m.addr_idx] = bd(n, d)
|
|
|
|
|
+ if idx:
|
|
|
|
|
+ assert len(set(chk_acc)) == len(chk_acc), chk_fail_msg
|
|
|
|
|
+ yield ad(idx, acct_idx, total, unlocked_total, d_acc)
|
|
|
|
|
+ self.accts_data = tuple(gen_accts_data())
|
|
|
|
|
+ return data
|
|
|
|
|
+
|
|
|
|
|
+ class display_type:
|
|
|
|
|
+
|
|
|
|
|
+ class squeezed(TwUnspentOutputs.display_type.squeezed):
|
|
|
|
|
+ cols = ('addr_idx', 'addr', 'comment', 'amt')
|
|
|
|
|
+ colhdr_fmt_method = None
|
|
|
|
|
+ fmt_method = 'gen_display'
|
|
|
|
|
+
|
|
|
|
|
+ class detail(TwUnspentOutputs.display_type.detail):
|
|
|
|
|
+ cols = ('addr_idx', 'addr', 'amt', 'comment')
|
|
|
|
|
+ colhdr_fmt_method = None
|
|
|
|
|
+ fmt_method = 'gen_display'
|
|
|
|
|
+ line_fmt_method = 'squeezed_format_line'
|
|
|
|
|
+
|
|
|
|
|
+ def get_column_widths(self, data, *, wide, interactive):
|
|
|
|
|
+ return self.compute_column_widths(
|
|
|
|
|
+ widths = { # fixed cols
|
|
|
|
|
+ 'addr_idx': MoneroIdx.max_digits,
|
|
|
|
|
+ 'used': 4 if 'used' in self.display_type.squeezed.cols else 0,
|
|
|
|
|
+ 'amt': self.amt_widths['amt'],
|
|
|
|
|
+ 'spc': len(self.display_type.squeezed.cols)},
|
|
|
|
|
+ maxws = { # expandable cols
|
|
|
|
|
+ 'addr': max(len(d.addr) for d in data),
|
|
|
|
|
+ 'comment': max(d.comment.screen_width for d in data)},
|
|
|
|
|
+ minws = {
|
|
|
|
|
+ 'addr': 16,
|
|
|
|
|
+ 'comment': len('Comment')},
|
|
|
|
|
+ maxws_nice = self.nice_addr_w,
|
|
|
|
|
+ wide = wide,
|
|
|
|
|
+ interactive = interactive)
|
|
|
|
|
+
|
|
|
|
|
+ def gen_display(self, data, cw, fs, color, fmt_method):
|
|
|
|
|
+ yes, no = (red('Used'), green('New ')) if color else ('Used', 'New ')
|
|
|
|
|
+ fs_acct = '{:>4} {:6} {:7} {}'
|
|
|
|
|
+ # 30 = 4(col1) + 6(col2) + 7(col3) + 8(iwidth) + 1(len('.')) + 4(spc)
|
|
|
|
|
+ rfill = ' ' * (self.term_width - self.proto.coin_amt.max_prec - 30)
|
|
|
|
|
+ yield fs_acct.format('', 'Wallet', 'Account', ' Balance').ljust(self.term_width)
|
|
|
|
|
+ for n, d in enumerate(self.accts_data):
|
|
|
|
|
+ yield fs_acct.format(
|
|
|
|
|
+ str(n + 1) + ')',
|
|
|
|
|
+ d.idx.fmt(6, color=color),
|
|
|
|
|
+ d.acct_idx.fmt(7, color=color),
|
|
|
|
|
+ d.total.fmt2(
|
|
|
|
|
+ 8, # iwidth
|
|
|
|
|
+ color = color,
|
|
|
|
|
+ color_override = None if d.total == d.unlocked_total else 'orange'
|
|
|
|
|
+ )) + rfill
|
|
|
|
|
+ for v in d.data.values():
|
|
|
|
|
+ yield fmt_method(None, v.data, cw, fs, color, yes, no)
|
|
|
|
|
+
|
|
|
|
|
+ def squeezed_format_line(self, n, d, cw, fs, color, yes, no):
|
|
|
|
|
+ return fs.format(
|
|
|
|
|
+ I = d.twmmid.obj.addr_idx.fmt(cw.addr_idx, color=color),
|
|
|
|
|
+ a = d.addr.fmt(self.addr_view_pref, cw.addr, color=color),
|
|
|
|
|
+ u = yes if d.is_used else no,
|
|
|
|
|
+ c = d.comment.fmt2(cw.comment, color=color, nullrepl='-'),
|
|
|
|
|
+ A = d.amt.fmt2(
|
|
|
|
|
+ cw.iwidth,
|
|
|
|
|
+ color = color,
|
|
|
|
|
+ color_override = None if d.amt == d.unlocked_amt else 'orange',
|
|
|
|
|
+ prec = self.disp_prec))
|
|
|
|
|
+
|
|
|
|
|
+ async def get_idx_from_user(self):
|
|
|
|
|
+ if res := await self.get_idx(f'{self.item_desc} number', self.accts_data):
|
|
|
|
|
+ return await self.get_idx(
|
|
|
|
|
+ 'address index',
|
|
|
|
|
+ self.accts_data[res.idx - 1].data,
|
|
|
|
|
+ is_addr_idx = True)
|
|
|
|
|
+
|
|
|
class action(TwView.action):
|
|
class action(TwView.action):
|
|
|
|
|
|
|
|
async def a_sync_wallets(self, parent):
|
|
async def a_sync_wallets(self, parent):
|