Browse Source

modified: mmgen/node_tools/Sound.py
modified: mmgen/node_tools/Util.py
modified: mmnode-netrate
modified: mmnode-peerblocks
modified: mmnode-play-sound

The MMGen Project 4 years ago
parent
commit
9f801a9cb3
5 changed files with 138 additions and 95 deletions
  1. 7 7
      mmgen/node_tools/Sound.py
  2. 1 0
      mmgen/node_tools/Util.py
  3. 22 13
      mmnode-netrate
  4. 104 70
      mmnode-peerblocks
  5. 4 5
      mmnode-play-sound

+ 7 - 7
mmgen/node_tools/Sound.py

@@ -42,22 +42,22 @@ def parse_repeat_spec(rs):
 def init_sound():
 def init_sound():
 	def _restore_sound():
 	def _restore_sound():
 #	msg('Restoring sound volume')
 #	msg('Restoring sound volume')
-		do_system('alsactl restore -f ' + _alsa_config_file)
+		do_system('sudo alsactl restore -f ' + _alsa_config_file)
 		os.unlink(_alsa_config_file)
 		os.unlink(_alsa_config_file)
 	import atexit
 	import atexit
 	atexit.register(_restore_sound)
 	atexit.register(_restore_sound)
-	do_system('alsactl store -f ' + _alsa_config_file)
+	do_system('sudo alsactl store -f ' + _alsa_config_file)
 
 
 def play_sound(fn,vol,repeat_spec='',remote_host='',kill_flg=None,testing=False):
 def play_sound(fn,vol,repeat_spec='',remote_host='',kill_flg=None,testing=False):
 	if not remote_host:
 	if not remote_host:
-		do_system('alsactl store -f ' + _alsa_config_file)
+		do_system('sudo alsactl store -f ' + _alsa_config_file)
 		for k in 'Master','Speaker','Headphone':
 		for k in 'Master','Speaker','Headphone':
-			do_system(('amixer -q set %s on' % k),testing)
+			do_system(('sudo amixer -q set %s on' % k),testing)
 #		do_system('amixer -q set Headphone off')
 #		do_system('amixer -q set Headphone off')
 
 
 		vols = dict([(k,int(_dvols[k] * float(vol) / 100)) for k in _dvols])
 		vols = dict([(k,int(_dvols[k] * float(vol) / 100)) for k in _dvols])
 		for k in vols:
 		for k in vols:
-			do_system('amixer -q set %s %s' % (k,vols[k]),testing)
+			do_system('sudo amixer -q set %s %s' % (k,vols[k]),testing)
 
 
 	fn = os.path.expanduser(fn)
 	fn = os.path.expanduser(fn)
 	cmd = (
 	cmd = (
@@ -72,9 +72,9 @@ def play_sound(fn,vol,repeat_spec='',remote_host='',kill_flg=None,testing=False)
 				do_system(cmd,testing)
 				do_system(cmd,testing)
 				if kill_flg.wait(interval):
 				if kill_flg.wait(interval):
 					if not remote_host:
 					if not remote_host:
-						do_system('alsactl restore -f ' + _alsa_config_file)
+						do_system('sudo alsactl restore -f ' + _alsa_config_file)
 					return
 					return
 	else: # Play once
 	else: # Play once
 		do_system(cmd,testing)
 		do_system(cmd,testing)
 		if not remote_host:
 		if not remote_host:
-			do_system('alsactl restore -f ' + _alsa_config_file)
+			do_system('sudo alsactl restore -f ' + _alsa_config_file)

+ 1 - 0
mmgen/node_tools/Util.py

@@ -20,6 +20,7 @@ mmgen.node_tools.Util: utility functions for MMGen node tools
 """
 """
 
 
 import time,subprocess
 import time,subprocess
+from mmgen.util import msg
 
 
 def get_hms(t=None,utc=False,no_secs=False):
 def get_hms(t=None,utc=False,no_secs=False):
 	secs = t or time.time()
 	secs = t or time.time()

+ 22 - 13
mmnode-netrate

@@ -1,7 +1,7 @@
 #!/usr/bin/env python3
 #!/usr/bin/env python3
 #
 #
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C)2013-2017 Philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2020 The MMGen Project <mmgen@tuta.io>
 #
 #
 # This program is free software: you can redistribute it and/or modify it under
 # This program is free software: you can redistribute it and/or modify it under
 # the terms of the GNU General Public License as published by the Free Software
 # the terms of the GNU General Public License as published by the Free Software
@@ -37,24 +37,33 @@ opts_data = {
 cmd_args = opts.init(opts_data)
 cmd_args = opts.init(opts_data)
 
 
 ERASE_LINE,CUR_UP = '\033[K','\033[1A'
 ERASE_LINE,CUR_UP = '\033[K','\033[1A'
-c = rpc_init()
 
 
-def do_loop():
-	def get_data():
-		d = c.getnettotals()
+async def main():
+	from mmgen.rpc import rpc_init
+	c = await rpc_init()
+
+	async def get_data():
+		d = await c.call('getnettotals')
 		return [float(e) for e in (d['totalbytesrecv'],d['totalbytessent'],d['timemillis'])]
 		return [float(e) for e in (d['totalbytesrecv'],d['totalbytessent'],d['timemillis'])]
 
 
-	r,s,t = get_data()
-	time.sleep(0.2)
+	rs = None
 	while True:
 	while True:
-		rs,ss,ts = r,s,t
-		r,s,t = get_data()
-		td = t-ts
-		sys.stderr.write('\rrcvd: {:9.2f} kB/s\nsent: {:9.2f} kB/s '.format((r-rs)/td,(s-ss)/td))
+		r,s,t = await get_data()
+
+		if rs is not None:
+			sys.stderr.write(
+				'\rrcvd: {:9.2f} kB/s\nsent: {:9.2f} kB/s '.format(
+					(r-rs)/(t-ts),
+					(s-ss)/(t-ts) ))
+
 		time.sleep(2)
 		time.sleep(2)
-		sys.stderr.write('{}{}{}'.format(ERASE_LINE,CUR_UP,ERASE_LINE))
+
+		if rs is not None:
+			sys.stderr.write('{}{}{}'.format(ERASE_LINE,CUR_UP,ERASE_LINE))
+
+		rs,ss,ts = r,s,t
 
 
 try:
 try:
-	do_loop()
+	run_session(main(),do_rpc_init=False)
 except KeyboardInterrupt:
 except KeyboardInterrupt:
 	sys.stderr.write('\n')
 	sys.stderr.write('\n')

+ 104 - 70
mmnode-peerblocks

@@ -1,7 +1,7 @@
 #!/usr/bin/env python3
 #!/usr/bin/env python3
 #
 #
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C)2013-2017 Philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2020 The MMGen Project <mmgen@tuta.io>
 #
 #
 # This program is free software: you can redistribute it and/or modify it under
 # This program is free software: you can redistribute it and/or modify it under
 # the terms of the GNU General Public License as published by the Free Software
 # the terms of the GNU General Public License as published by the Free Software
@@ -20,7 +20,7 @@
 mmgen-peerblocks: List blocks in flight, disconnect stalling nodes
 mmgen-peerblocks: List blocks in flight, disconnect stalling nodes
 """
 """
 
 
-import time,threading
+import asyncio
 from mmgen.common import *
 from mmgen.common import *
 
 
 opts_data = {
 opts_data = {
@@ -34,85 +34,119 @@ opts_data = {
 	}
 	}
 }
 }
 
 
-cmd_args = opts.init(opts_data)
+opts.init(opts_data)
 
 
-colors = ['\033[38;5;%s;1m' % c for c in (238,240,242,244,246,247,249,251,253,255)]
-_red,_reset = '\033[31m','\033[0m'
+async def inflight_display(rpc):
 
 
-ERASE_ALL,ERASE_LINE,CUR_HOME,CUR_HIDE,CUR_SHOW = \
-	'\033[J','\033[K','\033[H','\033[?25l','\033[?25h'
-
-import atexit
-def at_exit():
-	import os
-	os.system('stty sane')
-	sys.stderr.write('\n')
-atexit.register(at_exit)
-
-bc = rpc_init()
+	def gen_peers(peerinfo):
+		global min_height
+		min_height = None
+		for d in peerinfo:
+			if 'inflight' in d and d['inflight']:
+				blks = d['inflight']
+				if not min_height or min_height > blks[0]:
+					min_height = blks[0]
+				blks_trunc = ' '.join(map(str,blks))[:term_width-6].split()
+				trim = blks_trunc[-1] != str(blks[len(blks_trunc)-1])
+				blks = blks[:len(blks_trunc)-trim]
+			else:
+				blks = []
+			yield { 'id': d['id'], 'data': blks }
 
 
-msg_r(CUR_HOME+ERASE_ALL)
+	def gen_line(peer):
+		for blk in peer['data']:
+			if blk == min_height:
+				yield RED + str(blk) + RESET
+			else:
+				yield COLORS[blk % 10] + str(blk) + RESET
 
 
-def do_display():
 	from mmgen.term import get_terminal_size
 	from mmgen.term import get_terminal_size
-	global data
+	term_width = get_terminal_size()[0]
+
 	count = 1
 	count = 1
 	while True:
 	while True:
-		twid = get_terminal_size()[0]
-		data = bc.getpeerinfo()
-		min_t = None
-		lines = []
-		with lock:
-			msg('{}{}{}ACTIVE PEERS ({}) - poll {}'.format(
-				CUR_HOME,ERASE_ALL,CUR_HOME,len(data),count))
-			for d in data:
-				line = { 'id': d['id'], 'data': [] }
-				if 'inflight' in d and d['inflight']:
-					blks = [str(e) for e in d['inflight']]
-					min_p = min(e for e in d['inflight'])
-					if not min_t or min_t > min_p: min_t = min_p
-					line_d = ' '.join(blks)[:twid-6]
-					blks = blks[:len(line_d) - len(line_d.replace(' ','')) + 1]
-					blks[-1] = blks[-1][:len(line_d.split(' ')[-1])]
-					line['data'] = [[colors[int(i)%10],i,_reset] for i in blks if i]
-				else:
-					line['data'] = []
-				lines.append(line)
-			for line in lines:
-				d = ' '.join([(a,_red)[int(b)==min_t]+b+c for a,b,c in line['data']])
-				sys.stderr.write('\r{} {:>3}: {}\n'.format(ERASE_LINE,line['id'],d))
-			msg_r(ERASE_ALL+'Hit ENTER for disconnect prompt: ')
-		time.sleep(2)
+		info = await rpc.call('getpeerinfo')
+
+		msg_r(CUR_HOME+ERASE_ALL+CUR_HOME)
+		msg(f'ACTIVE PEERS ({len(info)}) - poll {count}')
+
+		for peer in gen_peers(info):
+			sys.stderr.write('\r{} {:>3}: {}\n'.format(
+				ERASE_LINE,
+				peer['id'],
+				' '.join(gen_line(peer)) ))
+
+		msg_r(ERASE_ALL+'Hit ENTER for disconnect prompt: ')
+		await asyncio.sleep(2)
 		count += 1
 		count += 1
 
 
-lock = threading.Lock()
-data = {}
+async def do_inflight(rpc):
+	task = asyncio.ensure_future(inflight_display(rpc)) # Python 3.7+: create_task()
+	from select import select
+
+	while True:
+		key = select([sys.stdin], [], [], 0.1)[0]
+		if key:
+			sys.stdin.read(1)
+			task.cancel()
+			break
+		await asyncio.sleep(0.1)
+
+	try:
+		await task
+	except asyncio.CancelledError:
+		pass
 
 
-t = threading.Thread(target=do_display,name='display')
-t.daemon = True
-t.start()
+async def do_disconnect_menu(rpc):
 
 
-def do_loop():
-	global data
 	while True:
 	while True:
-		input()
-		with lock:
-			ids = [str(d['id']) for d in data]
-			msg('{}{}{}ACTIVE PEERS ({})'.format(CUR_HOME,ERASE_ALL,CUR_HOME,len(data)))
-			msg(' '+'\n '.join(['{:>3}: {:30} {}'.format(*[d[k] for k in ('id','addr','subver')]) for d in data]))
-			reply = input('Enter a peer number to disconnect> ')
-			if reply == '':
-				pass
-			elif reply in ids:
-				idx = ids.index(reply)
-				msg("Disconnecting peer {} ('{}')".format(reply,data[idx]['addr']))
-				bc.disconnectnode(data[idx]['addr'])
-				time.sleep(1.5)
-			else:
-				msg("'{}': invalid peer number".format(reply))
-				time.sleep(0.5)
+		peerinfo = await rpc.call('getpeerinfo')
+		ids = [str(d['id']) for d in peerinfo]
+
+		msg_r(CUR_HOME+ERASE_ALL+CUR_HOME)
+		msg(f'ACTIVE PEERS ({len(peerinfo)})')
+
+		if peerinfo:
+			msg('\n'.join([f"{d['id']:>3}: {d['addr']:30} {d['subver']}" for d in peerinfo]))
+
+		reply = input("Peer number to disconnect, ENTER to quit menu, 'u' to update peer list> ")
+
+		if reply == '':
+			return
+		elif reply == 'u':
+			msg(f'Updating peer list')
+			await asyncio.sleep(0.5)
+		elif reply in ids:
+			addr = peerinfo[ids.index(reply)]['addr']
+			msg(f'Disconnecting peer {reply} ({addr})')
+			try:
+				await rpc.call('disconnectnode',addr)
+			except RPCFailure:
+				msg(f'Unable to disconnect peer {addr}')
+			await asyncio.sleep(1.5)
+		else:
+			msg(f'{reply!r}: invalid peer number')
+			await asyncio.sleep(0.5)
+
+async def main():
+
+	msg_r(CUR_HOME+ERASE_ALL)
+
+	from mmgen.rpc import rpc_init
+	rpc = await rpc_init()
+
+	while True:
+		await do_inflight(rpc)
+		await do_disconnect_menu(rpc)
+
+RED,RESET = ('\033[31m','\033[0m')
+COLORS = ['\033[38;5;%s;1m' % c for c in (238,240,242,244,246,247,249,251,253,255)]
+ERASE_ALL,ERASE_LINE,CUR_HOME,CUR_HIDE,CUR_SHOW = (
+	'\033[J','\033[K','\033[H','\033[?25l','\033[?25h')
 
 
 try:
 try:
-	do_loop()
-except KeyboardInterrupt:
-	pass
+	run_session(main(),do_rpc_init=False)
+except:
+	from subprocess import run
+	run(['stty','sane'])
+	msg('')

+ 4 - 5
mmnode-play-sound

@@ -1,7 +1,7 @@
 #!/usr/bin/env python3
 #!/usr/bin/env python3
 #
 #
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
 # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
-# Copyright (C)2013-2016 Philemon <mmgen-py@yandex.com>
+# Copyright (C)2013-2020 The MMGen Project <mmgen@tuta.io>
 #
 #
 # This program is free software: you can redistribute it and/or modify
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # it under the terms of the GNU General Public License as published by
@@ -32,6 +32,7 @@ opts_data = {
 		'usage': '[opts]',
 		'usage': '[opts]',
 		'options': """
 		'options': """
 -h, --help       Print this help message
 -h, --help       Print this help message
+-t, --testing    Test, don't execute shell commands
 -v, --volume=  n Adjust sound volume by percentage 'n' (default: {})
 -v, --volume=  n Adjust sound volume by percentage 'n' (default: {})
 """.format(volume)
 """.format(volume)
 	}
 	}
@@ -40,10 +41,8 @@ opts_data = {
 args = opts.init(opts_data)
 args = opts.init(opts_data)
 
 
 if opt.volume:
 if opt.volume:
-	volume = opt.volume
 	try:
 	try:
-		volume = int(volume)
-		assert 1 <= volume <= 120
+		assert 1 <= int(opt.volume) <= 120
 	except:
 	except:
 		die(1,'Sound volume must be an integer between 1 and 120')
 		die(1,'Sound volume must be an integer between 1 and 120')
 
 
@@ -53,4 +52,4 @@ if len(args) != 1:
 try:    os.stat(args[0])
 try:    os.stat(args[0])
 except: die(1,"Couldn't stat file '{}'".format(args[0]))
 except: die(1,"Couldn't stat file '{}'".format(args[0]))
 
 
-play_sound(args[0],volume)
+play_sound(args[0],int(opt.volume),testing=opt.testing)