allow interactive questions

This commit is contained in:
JerryXiao 2021-04-05 12:16:18 +08:00
parent cf45b3c490
commit ae982b78b0
Signed by: Jerry
GPG key ID: 22618F758B5BE2E5
2 changed files with 56 additions and 24 deletions

View file

@ -5,7 +5,7 @@ import subprocess
import logging import logging
from re import match from re import match
import json import json
from os import environ, getuid from os import environ, getuid, isatty
import traceback import traceback
import pyalpm import pyalpm
import pycman import pycman
@ -75,7 +75,7 @@ class alpmCallback:
handle.questioncb = self.noop handle.questioncb = self.noop
handle.progresscb = self.noop handle.progresscb = self.noop
def upgrade() -> List[str]: def upgrade(interactive=False) -> List[str]:
logger.info('upgrade start') logger.info('upgrade start')
pycman.config.cb_log = lambda *_: None pycman.config.cb_log = lambda *_: None
handle = pycman.config.init_with_config(PACMAN_CONFIG) handle = pycman.config.init_with_config(PACMAN_CONFIG)
@ -108,11 +108,11 @@ def upgrade() -> List[str]:
examine_upgrade(t.to_add, t.to_remove) examine_upgrade(t.to_add, t.to_remove)
finally: finally:
t.release() t.release()
pacman_output = execute_with_io(['pacman', '-Su', '--noprogressbar', '--color', 'never'], UPGRADE_TIMEOUT) pacman_output = execute_with_io(['pacman', '-Su', '--noprogressbar', '--color', 'never'], UPGRADE_TIMEOUT, interactive=interactive)
logger.info('upgrade end') logger.info('upgrade end')
return pacman_output return pacman_output
def do_system_upgrade(debug=False) -> checkReport: def do_system_upgrade(debug=False, interactive=False) -> checkReport:
for _ in range(NETWORK_RETRY): for _ in range(NETWORK_RETRY):
try: try:
sync() sync()
@ -127,7 +127,7 @@ def do_system_upgrade(debug=False) -> checkReport:
try: try:
with open(PACMAN_LOG, 'r') as pacman_log: with open(PACMAN_LOG, 'r') as pacman_log:
log_anchor = pacman_log.seek(0, 2) log_anchor = pacman_log.seek(0, 2)
stdout = upgrade() stdout = upgrade(interactive=interactive)
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
if upgrade_err_is_net(e.output): if upgrade_err_is_net(e.output):
logger.warning('upgrade download failed') logger.warning('upgrade download failed')
@ -240,12 +240,15 @@ def main() -> None:
parser.add_argument('-d', '--debug', action='store_true', help='enable debug mode') parser.add_argument('-d', '--debug', action='store_true', help='enable debug mode')
parser.add_argument('-v', '--verbose', action='store_true', help='show verbose report') parser.add_argument('-v', '--verbose', action='store_true', help='show verbose report')
parser.add_argument('-m', '--max', type=int, default=1, help='Number of upgrades to show') parser.add_argument('-m', '--max', type=int, default=1, help='Number of upgrades to show')
parser.add_argument('-i', '--interactive', choices=['auto', 'on', 'off'], default='auto', help='allow interactive questions')
args = parser.parse_args() args = parser.parse_args()
if args.debug: if args.debug:
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(module)s - %(funcName)s - %(levelname)s - %(message)s') logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(module)s - %(funcName)s - %(levelname)s - %(message)s')
else: else:
logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(message)s') logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(message)s')
locale_set() locale_set()
interactive = args.interactive == "on" or not (args.interactive == 'off' or not isatty(0))
logger.debug(f"interactive questions {'enabled' if interactive else 'disabled'}")
if args.action == 'run': if args.action == 'run':
if getuid() != 0: if getuid() != 0:
@ -262,7 +265,7 @@ def main() -> None:
logger.error(f'Database is locked at {PACMAN_DB_LCK}') logger.error(f'Database is locked at {PACMAN_DB_LCK}')
exit(2) exit(2)
try: try:
report = do_system_upgrade(args.debug) report = do_system_upgrade(debug=args.debug, interactive=interactive)
except NonFatal: except NonFatal:
raise raise
except Exception as e: except Exception as e:

View file

@ -6,6 +6,8 @@ from io import DEFAULT_BUFFER_SIZE
from time import mktime from time import mktime
from datetime import datetime from datetime import datetime
from signal import SIGINT, SIGTERM, Signals from signal import SIGINT, SIGTERM, Signals
from select import select
from sys import stdin
logger = logging.getLogger() logger = logging.getLogger()
class UnknownQuestionError(subprocess.SubprocessError): class UnknownQuestionError(subprocess.SubprocessError):
@ -15,7 +17,7 @@ class UnknownQuestionError(subprocess.SubprocessError):
def __str__(self): def __str__(self):
return f"Pacman returned an unknown question {self.question}" return f"Pacman returned an unknown question {self.question}"
def execute_with_io(command: List[str], timeout: int = 3600) -> List[str]: def execute_with_io(command: List[str], timeout: int = 3600, interactive: bool = False) -> List[str]:
''' '''
captures stdout and stderr and captures stdout and stderr and
automatically handles [y/n] questions of pacman automatically handles [y/n] questions of pacman
@ -42,23 +44,50 @@ def execute_with_io(command: List[str], timeout: int = 3600) -> List[str]:
stderr=subprocess.STDOUT, stderr=subprocess.STDOUT,
encoding='utf-8' encoding='utf-8'
) )
Thread(target=set_timeout, args=(p, timeout), daemon=True).start() # should be configurable try:
line = '' Thread(target=set_timeout, args=(p, timeout), daemon=True).start()
output = '' line = ''
while (r := p.stdout.read(1)) != '': output = ''
output += r while (r := p.stdout.read(1)) != '':
line += r output += r
if r == '\n': line += r
logger.debug('STDOUT: %s', line[:-1]) if r == '\n':
line = '' logger.debug('STDOUT: %s', line[:-1])
elif r == ']': line = ''
if line == ':: Proceed with installation? [Y/n]': elif r == ']':
p.stdin.write('y\n') if line == ':: Proceed with installation? [Y/n]':
p.stdin.flush() p.stdin.write('y\n')
elif line.lower().endswith('[y/n]'): p.stdin.flush()
terminate(p, signal=SIGINT) elif line.lower().endswith('[y/n]'):
raise UnknownQuestionError(line, output) if interactive:
print(f"Please answer this question in 60 seconds:\n{line}", end=' ', flush=True)
while True:
read_ready, _, _ = select([stdin], list(), list(), 60)
if not read_ready:
terminate(p, signal=SIGINT)
raise UnknownQuestionError(line, output)
choice = read_ready[0].readline().strip()
if choice.lower().startswith('y'):
p.stdin.write('y\n')
p.stdin.flush()
break
elif choice.lower().startswith('n'):
p.stdin.write('n\n')
p.stdin.flush()
break
else:
if choice.lower().startswith('s'):
print(output)
print("Please give an explicit answer [Y]es [N]o [S]how", end=' ', flush=True)
else:
terminate(p, signal=SIGINT)
raise UnknownQuestionError(line, output)
except KeyboardInterrupt:
terminate(p, signal=SIGINT)
raise
except Exception:
terminate(p)
raise
if (ret := p.wait()) != 0: if (ret := p.wait()) != 0:
raise subprocess.CalledProcessError(ret, command, output) raise subprocess.CalledProcessError(ret, command, output)
return output.split('\n') return output.split('\n')