|
@@ -40,7 +40,7 @@ from ..util import (
|
|
|
)
|
|
|
from ..base_obj import AsyncInit
|
|
|
from ..objmethods import MMGenObject
|
|
|
-from ..obj import ImmutableAttr,ListItemAttr,MMGenListItem,TwComment,get_obj,HexStr,CoinTxID,MMGenIdx
|
|
|
+from ..obj import ImmutableAttr,ListItemAttr,MMGenListItem,TwComment,get_obj,HexStr,CoinTxID,MMGenIdx,MMGenList
|
|
|
from ..addr import CoinAddr,MMGenID
|
|
|
from ..rpc import rpc_init
|
|
|
from .common import TwCommon,TwMMGenID,get_tw_label
|
|
@@ -51,11 +51,6 @@ class TwUnspentOutputs(MMGenObject,TwCommon,metaclass=AsyncInit):
|
|
|
return MMGenObject.__new__(base_proto_tw_subclass(cls,proto,'unspent'))
|
|
|
|
|
|
txid_w = 64
|
|
|
- age_fmts_date_dependent = ('days','date','date_time')
|
|
|
- age_fmts_interactive = ('confs','block','days','date')
|
|
|
- _age_fmt = 'confs'
|
|
|
-
|
|
|
- class MMGenTwOutputList(list,MMGenObject): pass
|
|
|
|
|
|
class MMGenTwUnspentOutput(MMGenListItem):
|
|
|
txid = ListItemAttr(CoinTxID)
|
|
@@ -82,35 +77,15 @@ class TwUnspentOutputs(MMGenObject,TwCommon,metaclass=AsyncInit):
|
|
|
|
|
|
async def __init__(self,proto,minconf=1,addrs=[]):
|
|
|
self.proto = proto
|
|
|
- self.data = self.MMGenTwOutputList()
|
|
|
- self.fmt_display = ''
|
|
|
- self.fmt_print = ''
|
|
|
- self.cols = None
|
|
|
- self.reverse = False
|
|
|
- self.group = False
|
|
|
+ self.data = MMGenList()
|
|
|
self.show_mmid = True
|
|
|
self.minconf = minconf
|
|
|
self.addrs = addrs
|
|
|
- self.sort_key = 'age'
|
|
|
- self.disp_prec = self.get_display_precision()
|
|
|
self.rpc = await rpc_init(proto)
|
|
|
|
|
|
from .ctl import TrackingWallet
|
|
|
self.wallet = await TrackingWallet(proto,mode='w')
|
|
|
|
|
|
- @property
|
|
|
- def age_fmt(self):
|
|
|
- return self._age_fmt
|
|
|
-
|
|
|
- @age_fmt.setter
|
|
|
- def age_fmt(self,val):
|
|
|
- if val not in self.age_fmts:
|
|
|
- die( 'BadAgeFormat', f'{val!r}: invalid age format (must be one of {self.age_fmts!r})' )
|
|
|
- self._age_fmt = val
|
|
|
-
|
|
|
- def get_display_precision(self):
|
|
|
- return self.proto.coin_amt.max_prec
|
|
|
-
|
|
|
@property
|
|
|
def total(self):
|
|
|
return sum(i.amt for i in self.data)
|
|
@@ -144,45 +119,13 @@ class TwUnspentOutputs(MMGenObject,TwCommon,metaclass=AsyncInit):
|
|
|
self.proto,
|
|
|
**{ k:v for k,v in o.items() if k in self.MMGenTwUnspentOutput.valid_attrs } )
|
|
|
|
|
|
- self.data = self.MMGenTwOutputList(gen_unspent())
|
|
|
+ self.data = MMGenList(gen_unspent())
|
|
|
|
|
|
if not self.data:
|
|
|
- die(1, f'No tracked {self.item_desc}s in tracking wallet!')
|
|
|
+ die(1,self.no_data_errmsg)
|
|
|
|
|
|
self.do_sort(key=sort_key,reverse=reverse_sort)
|
|
|
|
|
|
- def do_sort(self,key=None,reverse=False):
|
|
|
- sort_funcs = {
|
|
|
- 'addr': lambda i: i.addr,
|
|
|
- 'age': lambda i: 0 - i.confs,
|
|
|
- 'amt': lambda i: i.amt,
|
|
|
- 'txid': lambda i: f'{i.txid} {i.vout:04}',
|
|
|
- 'twmmid': lambda i: i.twmmid.sort_key
|
|
|
- }
|
|
|
- key = key or self.sort_key
|
|
|
- if key not in sort_funcs:
|
|
|
- die(1,f'{key!r}: invalid sort key. Valid options: {" ".join(sort_funcs.keys())}')
|
|
|
- self.sort_key = key
|
|
|
- assert type(reverse) == bool
|
|
|
- self.data.sort(key=sort_funcs[key],reverse=reverse or self.reverse)
|
|
|
-
|
|
|
- def sort_info(self,include_group=True):
|
|
|
- ret = ([],['Reverse'])[self.reverse]
|
|
|
- ret.append(capfirst(self.sort_key).replace('Twmmid','MMGenID'))
|
|
|
- if include_group and self.group and (self.sort_key in ('addr','txid','twmmid')):
|
|
|
- ret.append('Grouped')
|
|
|
- return ret
|
|
|
-
|
|
|
- def set_term_columns(self):
|
|
|
- from ..term import get_terminal_size
|
|
|
- while True:
|
|
|
- self.cols = g.terminal_width or get_terminal_size().width
|
|
|
- if self.cols >= g.min_screen_width:
|
|
|
- break
|
|
|
- line_input(
|
|
|
- 'Screen too narrow to display the tracking wallet\n'
|
|
|
- + f'Please resize your screen to at least {g.min_screen_width} characters and hit ENTER ' )
|
|
|
-
|
|
|
def get_display_constants(self):
|
|
|
data = self.data
|
|
|
for i in data:
|
|
@@ -221,7 +164,10 @@ class TwUnspentOutputs(MMGenObject,TwCommon,metaclass=AsyncInit):
|
|
|
b.skip = (k,'addr')[k=='twmmid']
|
|
|
|
|
|
def gen_output():
|
|
|
- yield self.hdr_fmt.format(' '.join(self.sort_info()),self.proto.dcoin,self.total.hl())
|
|
|
+ yield self.hdr_fmt.format(
|
|
|
+ a = ' '.join(self.sort_info()),
|
|
|
+ b = self.proto.dcoin,
|
|
|
+ c = self.total.hl() if hasattr(self,'total') else None )
|
|
|
if self.proto.chain_name != 'mainnet':
|
|
|
yield 'Chain: '+green(self.proto.chain_name.upper())
|
|
|
fs = self.display_fs_fs.format( cw=c.col1_w, tw=c.tx_w )
|
|
@@ -385,99 +331,3 @@ class TwUnspentOutputs(MMGenObject,TwCommon,metaclass=AsyncInit):
|
|
|
fs = 'Refreshing tracking wallet {} #{}. Is this what you want?'
|
|
|
if keypress_confirm(fs.format(self.item_desc,n)):
|
|
|
return n
|
|
|
-
|
|
|
- async def view_and_sort(self,tx):
|
|
|
- from ..term import get_char
|
|
|
- prompt = self.prompt.strip() + '\b'
|
|
|
- no_output,oneshot_msg = False,None
|
|
|
- from ..opts import opt
|
|
|
- CUR_HOME,ERASE_ALL = '\033[H','\033[0J'
|
|
|
- CUR_RIGHT = lambda n: f'\033[{n}C'
|
|
|
-
|
|
|
- while True:
|
|
|
- msg_r('' if no_output else '\n\n' if opt.no_blank else CUR_HOME+ERASE_ALL)
|
|
|
- reply = get_char(
|
|
|
- '' if no_output else await self.format_for_display()+'\n'+(oneshot_msg or '')+prompt,
|
|
|
- immed_chars=''.join(self.key_mappings.keys())
|
|
|
- )
|
|
|
- no_output = False
|
|
|
- oneshot_msg = '' if oneshot_msg else None # tristate, saves previous state
|
|
|
- if reply not in self.key_mappings:
|
|
|
- msg_r('\ninvalid keypress ')
|
|
|
- time.sleep(0.5)
|
|
|
- continue
|
|
|
-
|
|
|
- action = self.key_mappings[reply]
|
|
|
- if action[:2] == 's_':
|
|
|
- self.do_sort(action[2:])
|
|
|
- if action == 's_twmmid': self.show_mmid = True
|
|
|
- elif action == 'd_days':
|
|
|
- af = self.age_fmts_interactive
|
|
|
- self.age_fmt = af[(af.index(self.age_fmt) + 1) % len(af)]
|
|
|
- elif action == 'd_mmid':
|
|
|
- self.show_mmid = not self.show_mmid
|
|
|
- elif action == 'd_group':
|
|
|
- if self.can_group:
|
|
|
- self.group = not self.group
|
|
|
- elif action == 'd_redraw':
|
|
|
- pass
|
|
|
- elif action == 'd_reverse':
|
|
|
- self.data.reverse()
|
|
|
- self.reverse = not self.reverse
|
|
|
- elif action == 'a_quit':
|
|
|
- msg('')
|
|
|
- return self.data
|
|
|
- elif action == 'a_balance_refresh':
|
|
|
- idx = self.get_idx_from_user(action)
|
|
|
- if idx:
|
|
|
- e = self.data[idx-1]
|
|
|
- bal = await self.wallet.get_balance(e.addr,force_rpc=True)
|
|
|
- await self.get_data()
|
|
|
- oneshot_msg = yellow(f'{self.proto.dcoin} balance for account #{idx} refreshed\n\n')
|
|
|
- self.display_constants = self.get_display_constants()
|
|
|
- elif action == 'a_lbl_add':
|
|
|
- idx,lbl = self.get_idx_from_user(action)
|
|
|
- if idx:
|
|
|
- e = self.data[idx-1]
|
|
|
- if await self.wallet.add_label(e.twmmid,lbl,addr=e.addr):
|
|
|
- await self.get_data()
|
|
|
- oneshot_msg = yellow('Label {} {} #{}\n\n'.format(
|
|
|
- ('added to' if lbl else 'removed from'),
|
|
|
- self.item_desc,
|
|
|
- idx ))
|
|
|
- else:
|
|
|
- oneshot_msg = red('Label could not be added\n\n')
|
|
|
- self.display_constants = self.get_display_constants()
|
|
|
- elif action == 'a_addr_delete':
|
|
|
- idx = self.get_idx_from_user(action)
|
|
|
- if idx:
|
|
|
- e = self.data[idx-1]
|
|
|
- if await self.wallet.remove_address(e.addr):
|
|
|
- await self.get_data()
|
|
|
- oneshot_msg = yellow(f'{capfirst(self.item_desc)} #{idx} removed\n\n')
|
|
|
- else:
|
|
|
- oneshot_msg = red('Address could not be removed\n\n')
|
|
|
- self.display_constants = self.get_display_constants()
|
|
|
- elif action == 'a_print':
|
|
|
- of = '{}-{}[{}].out'.format(
|
|
|
- self.dump_fn_pfx,
|
|
|
- self.proto.dcoin,
|
|
|
- ','.join(self.sort_info(include_group=False)).lower() )
|
|
|
- msg('')
|
|
|
- from ..fileutil import write_data_to_file
|
|
|
- try:
|
|
|
- write_data_to_file(
|
|
|
- of,
|
|
|
- await self.format_for_printing(),
|
|
|
- desc = f'{self.desc} listing' )
|
|
|
- except UserNonConfirmation as e:
|
|
|
- oneshot_msg = red(f'File {of!r} not overwritten by user request\n\n')
|
|
|
- else:
|
|
|
- oneshot_msg = yellow(f'Data written to {of!r}\n\n')
|
|
|
- elif action in ('a_view','a_view_wide'):
|
|
|
- do_pager(
|
|
|
- self.fmt_display if action == 'a_view' else
|
|
|
- await self.format_for_printing(color=True) )
|
|
|
- if g.platform == 'linux' and oneshot_msg == None:
|
|
|
- msg_r(CUR_RIGHT(len(prompt.split('\n')[-1])-2))
|
|
|
- no_output = True
|