From 94c0bd2dfeb801087c7a03787a964b15b2a37bd8 Mon Sep 17 00:00:00 2001 From: V3n3RiX Date: Mon, 26 Feb 2024 13:02:07 +0000 Subject: * extend https://bugs.redcorelinux.org/show_bug.cgi?id=143 * improve the UX at package removal stage and orphan removal stage * require double confirmation for a forced, unsafe removal of a package --- src/backend/autormpkgsrc.py | 74 +++++++++++++++++--- src/backend/rmpkgsrc.py | 161 ++++++++++++++++++++++++++++++++++++-------- 2 files changed, 197 insertions(+), 38 deletions(-) diff --git a/src/backend/autormpkgsrc.py b/src/backend/autormpkgsrc.py index 3f0c852..d696757 100644 --- a/src/backend/autormpkgsrc.py +++ b/src/backend/autormpkgsrc.py @@ -1,8 +1,11 @@ #!/usr/bin/python3 import atexit +import fcntl import io +import os import signal +import selectors import subprocess import sys import sisyphus.checkenv @@ -11,6 +14,26 @@ import sisyphus.killemerge import sisyphus.syncdb +def set_nonblocking(fd): + flags = fcntl.fcntl(fd, fcntl.F_GETFL) + fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK) + + +def spinner_animation(): + spinner = ['-', '\\', '|', '/'] + sel = selectors.DefaultSelector() + sel.register(sys.stdin, selectors.EVENT_READ) + + for _ in range(10): + for char in spinner: + sys.stdout.write('\b' + char) + sys.stdout.flush() + events = sel.select(timeout=0.1) + if events: + return + sys.stdout.write('\b') + + def sigint_handler(signal, frame): sys.exit(0) @@ -22,17 +45,46 @@ def start(gfx_ui=False): args = ['--quiet', '--depclean'] if sisyphus.checkenv.root() and not gfx_ui: - p_exe = subprocess.Popen(['emerge'] + args + ['--ask']) - try: - p_exe.wait() - sisyphus.syncdb.lcl_tbl() - except KeyboardInterrupt: - p_exe.terminate() - try: - p_exe.wait(1) - except subprocess.TimeoutExpired: - p_exe.kill() - sys.exit() + print("\n" + sisyphus.getclr.bright_white + "Orphaned, no longer needed packages are slated for" + sisyphus.getclr.reset + " " + + sisyphus.getclr.green + "'safe'" + sisyphus.getclr.reset + " " + sisyphus.getclr.bright_white + "removal." + sisyphus.getclr.reset + "\n") + while True: + user_input = input(sisyphus.getclr.bright_white + "Would you like to proceed?" + sisyphus.getclr.reset + " " + + "[" + sisyphus.getclr.bright_green + "Yes" + sisyphus.getclr.reset + "/" + sisyphus.getclr.bright_red + "No" + sisyphus.getclr.reset + "]" + " ") + if user_input.lower() in ['yes', 'y', '']: + p_exe = subprocess.Popen(['emerge'] + args) + try: + set_nonblocking(sys.stdout.fileno()) + spinner_animation() + + sel = selectors.DefaultSelector() + sel.register(sys.stdin, selectors.EVENT_READ) + + while True: + events = sel.select(timeout=0.1) + for key, mask in events: + if key.fileobj == sys.stdin: + line = sys.stdin.readline().strip() + if line.lower() == 'q': + sys.exit() + if p_exe.poll() is not None: + break + except KeyboardInterrupt: + p_exe.terminate() + try: + p_exe.wait(1) + except subprocess.TimeoutExpired: + p_exe.kill() + sys.exit() + finally: + p_exe.wait() + sisyphus.syncdb.lcl_tbl() + break + elif user_input.lower() in ['no', 'n']: + break + else: + print("\nSorry, response" + " " + "'" + + user_input + "'" + " " + "not understood.\n") + continue elif gfx_ui: p_exe = subprocess.Popen( ['emerge'] + args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) diff --git a/src/backend/rmpkgsrc.py b/src/backend/rmpkgsrc.py index 17d5a12..1a7ecd6 100644 --- a/src/backend/rmpkgsrc.py +++ b/src/backend/rmpkgsrc.py @@ -1,10 +1,12 @@ #!/usr/bin/python3 import atexit +import fcntl import io import os import pickle import signal +import selectors import subprocess import sys import sisyphus.checkenv @@ -15,6 +17,26 @@ import sisyphus.solverevdeps import sisyphus.syncdb +def set_nonblocking(fd): + flags = fcntl.fcntl(fd, fcntl.F_GETFL) + fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK) + + +def spinner_animation(): + spinner = ['-', '\\', '|', '/'] + sel = selectors.DefaultSelector() + sel.register(sys.stdin, selectors.EVENT_READ) + + for _ in range(10): + for char in spinner: + sys.stdout.write('\b' + char) + sys.stdout.flush() + events = sel.select(timeout=0.1) + if events: + return + sys.stdout.write('\b') + + def sigint_handler(signal, frame): sys.exit(0) @@ -54,7 +76,21 @@ def start(pkgname, depclean=False, gfx_ui=False, unmerge=False): p_exe = subprocess.Popen( ['emerge'] + args + ['--pretend', '--verbose'] + list(pkgname)) try: - p_exe.wait() + set_nonblocking(sys.stdout.fileno()) + spinner_animation() + + sel = selectors.DefaultSelector() + sel.register(sys.stdin, selectors.EVENT_READ) + + while True: + events = sel.select(timeout=0.1) + for key, mask in events: + if key.fileobj == sys.stdin: + line = sys.stdin.readline().strip() + if line.lower() == 'q': + sys.exit() + if p_exe.poll() is not None: + break except KeyboardInterrupt: p_exe.terminate() try: @@ -62,24 +98,66 @@ def start(pkgname, depclean=False, gfx_ui=False, unmerge=False): except subprocess.TimeoutExpired: p_exe.kill() sys.exit() + finally: + p_exe.wait() print(sisyphus.getclr.bright_red + "\nWon't uninstall! Other packages depend on " + sisyphus.getclr.reset + str(pkgname)) - print(sisyphus.getclr.bright_red + "Use the " + sisyphus.getclr.reset + sisyphus.getclr.green + "'--force'" + - sisyphus.getclr.reset + sisyphus.getclr.bright_red + " option to override at your own risk!\n" + sisyphus.getclr.reset) + print(sisyphus.getclr.bright_white + "Use the " + sisyphus.getclr.reset + sisyphus.getclr.green + "'--force'" + + sisyphus.getclr.reset + sisyphus.getclr.bright_white + " option to override at your own risk!\n" + sisyphus.getclr.reset) else: if unmerge: - p_exe = subprocess.Popen( - ['emerge', '--quiet', '--unmerge', '--ask'] + list(pkgname)) - try: - p_exe.wait() - sisyphus.syncdb.lcl_tbl() - except KeyboardInterrupt: - p_exe.terminate() - try: - p_exe.wait(1) - except subprocess.TimeoutExpired: - p_exe.kill() - sys.exit() + print("\n" + sisyphus.getclr.bright_white + "Selected packages are slated for" + sisyphus.getclr.reset + " " + sisyphus.getclr.green + + "'forced'" + sisyphus.getclr.reset + " " + sisyphus.getclr.bright_white + "removal." + sisyphus.getclr.reset + "\n") + while True: + user_input = input(sisyphus.getclr.bright_white + "Would you like to proceed?" + sisyphus.getclr.reset + " " + + "[" + sisyphus.getclr.bright_green + "Yes" + sisyphus.getclr.reset + "/" + sisyphus.getclr.bright_red + "No" + sisyphus.getclr.reset + "]" + " ") + if user_input.lower() in ['yes', 'y', '']: + while True: + confirmation_input = input(sisyphus.getclr.bright_white + "Are you sure you would like to proceed?" + sisyphus.getclr.reset + " " + + "[" + sisyphus.getclr.bright_green + "Yes" + sisyphus.getclr.reset + "/" + sisyphus.getclr.bright_red + "No" + sisyphus.getclr.reset + "]" + " ") + if confirmation_input.lower() in ['yes', 'y', '']: + p_exe = subprocess.Popen( + ['emerge', '--quiet', '--unmerge'] + list(pkgname)) + try: + set_nonblocking(sys.stdout.fileno()) + spinner_animation() + + sel = selectors.DefaultSelector() + sel.register(sys.stdin, selectors.EVENT_READ) + + while True: + events = sel.select(timeout=0.1) + for key, mask in events: + if key.fileobj == sys.stdin: + line = sys.stdin.readline().strip() + if line.lower() == 'q': + sys.exit() + if p_exe.poll() is not None: + break + except KeyboardInterrupt: + p_exe.terminate() + try: + p_exe.wait(1) + except subprocess.TimeoutExpired: + p_exe.kill() + sys.exit() + finally: + p_exe.wait() + sisyphus.syncdb.lcl_tbl() + break + elif confirmation_input.lower() in ['no', 'n']: + break + else: + print("\nSorry, response" + " " + "'" + + confirmation_input + "'" + " " + "not understood.\n") + continue + break + elif user_input.lower() in ['no', 'n']: + break + else: + print("\nSorry, response" + " " + "'" + + user_input + "'" + " " + "not understood.\n") + continue elif depclean: if gfx_ui: p_exe = subprocess.Popen( @@ -93,15 +171,44 @@ def start(pkgname, depclean=False, gfx_ui=False, unmerge=False): p_exe.wait() sisyphus.syncdb.lcl_tbl() else: - p_exe = subprocess.Popen( - ['emerge'] + args + ['--ask'] + list(pkgname)) - try: - p_exe.wait() - sisyphus.syncdb.lcl_tbl() - except KeyboardInterrupt: - p_exe.terminate() - try: - p_exe.wait(1) - except subprocess.TimeoutExpired: - p_exe.kill() - sys.exit() + print("\n" + sisyphus.getclr.bright_white + "Selected packages are slated for" + sisyphus.getclr.reset + " " + sisyphus.getclr.green + + "'safe'" + sisyphus.getclr.reset + " " + sisyphus.getclr.bright_white + "removal." + sisyphus.getclr.reset + "\n") + while True: + user_input = input(sisyphus.getclr.bright_white + "Would you like to proceed?" + sisyphus.getclr.reset + " " + + "[" + sisyphus.getclr.bright_green + "Yes" + sisyphus.getclr.reset + "/" + sisyphus.getclr.bright_red + "No" + sisyphus.getclr.reset + "]" + " ") + if user_input.lower() in ['yes', 'y', '']: + p_exe = subprocess.Popen( + ['emerge'] + args + list(pkgname)) + try: + set_nonblocking(sys.stdout.fileno()) + spinner_animation() + + sel = selectors.DefaultSelector() + sel.register(sys.stdin, selectors.EVENT_READ) + + while True: + events = sel.select(timeout=0.1) + for key, mask in events: + if key.fileobj == sys.stdin: + line = sys.stdin.readline().strip() + if line.lower() == 'q': + sys.exit() + if p_exe.poll() is not None: + break + except KeyboardInterrupt: + p_exe.terminate() + try: + p_exe.wait(1) + except subprocess.TimeoutExpired: + p_exe.kill() + sys.exit() + finally: + p_exe.wait() + sisyphus.syncdb.lcl_tbl() + break + elif user_input.lower() in ['no', 'n']: + break + else: + print("\nSorry, response" + " " + "'" + + user_input + "'" + " " + "not understood.\n") + continue -- cgit v1.2.3