From 8aed22de6967da59506bb077dd7fc672dff9dd6a Mon Sep 17 00:00:00 2001 From: Tilman Kranz Date: Thu, 20 Jul 2023 06:22:19 +0200 Subject: [PATCH] run subprocesses in threads --- bin/simple-apt-update | 102 +++++++++++++++++++++++++----------------- 1 file changed, 61 insertions(+), 41 deletions(-) diff --git a/bin/simple-apt-update b/bin/simple-apt-update index b25f1e1..2e654b4 100755 --- a/bin/simple-apt-update +++ b/bin/simple-apt-update @@ -1,5 +1,8 @@ #!/usr/bin/env python3 +import re +import queue +import threading import gi import html import logging @@ -12,18 +15,30 @@ import sys gi.require_version('Gdk', '3.0') gi.require_version('Gtk', '3.0') -try: - from gi.repository import Gtk -except ImportError: - logging.error("Could not import Gtk") - sys.exit(1) - try: from gi.repository import Gio except ImportError: logging.error("Could not import Gio") sys.exit(1) +# try: +# from gi.repository import GObject +# except ImportError: +# logging.error("Could not import GObject") +# sys.exit(1) + +try: + from gi.repository import GLib +except ImportError: + logging.error("Could not import Gio") + sys.exit(1) + +try: + from gi.repository import Gtk +except ImportError: + logging.error("Could not import Gtk") + sys.exit(1) + class UpdateWindow(Gtk.ApplicationWindow): def clear(self): @@ -81,14 +96,17 @@ class UpdateWindow(Gtk.ApplicationWindow): def insert(self, text, iter): self.buffer.insert(iter, text + "\n") - def run(self, args, ignore_stderr=False, output_msg=None, - empty_msg=None, env={}): + def execute(self, args, ignore_stderr=False, output_msg=None, + empty_msg=None, env={}): self.lock() self.clear() self.prepend_mesg( "INFO", "Running command \"%s\" ..." % " ".join(args)) + thread = threading.Thread(target=self.run, args=(args, env,)) + thread.start() + def run(self, args, env={}): p = subprocess.Popen( args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=False, env=dict(os.environ, **env)) @@ -97,8 +115,6 @@ class UpdateWindow(Gtk.ApplicationWindow): sel.register(p.stdout, selectors.EVENT_READ) sel.register(p.stderr, selectors.EVENT_READ) done = False - output = False - error = False while not done: for key, _ in sel.select(): @@ -106,39 +122,17 @@ class UpdateWindow(Gtk.ApplicationWindow): if data: if key.fileobj is p.stdout: - logging.debug("STDOUT: " + data) - self.append_mesg("STDOUT", data) - output = True - elif not ignore_stderr: - logging.debug("STDERR: " + data) - self.append_mesg("STDERR", data) + self.stdout_queue.put(data) + else: + self.stderr_queue.put(data) exit_code = p.poll() if exit_code is not None: - if exit_code != 0: - self.append_mesg( - "ERROR", - "apt-get exit code: %d\n" % exit_code - ) - error = True - + self.stdout_queue.put("EXIT %d" % exit_code) done = True break - self.unlock() - - if error: - return False - else: - if output: - if output_msg is not None: - self.prepend_mesg("INFO", output_msg) - elif empty_msg is not None: - self.append_mesg("INFO", empty_msg) - - return True - def lock(self): self.update_button.set_sensitive(False) self.upgrade_button.set_sensitive(False) @@ -154,7 +148,7 @@ class UpdateWindow(Gtk.ApplicationWindow): def upgrade(self): args = ['/usr/bin/apt-get', '-yqq', 'full-upgrade'] env = {'DEBIAN_FRONTEND': 'noninteractive'} - self.run( + self.execute( args, env=env, empty_msg="No package upgrades were performed.") @@ -165,14 +159,14 @@ class UpdateWindow(Gtk.ApplicationWindow): def update(self): args = ['/usr/bin/apt-get', '-y', 'update'] env = {'DEBIAN_FRONTEND': 'noninteractive'} - self.run(args, env=env) + self.execute(args, env=env) def on_update(self, *args): self.update() def list(self): args = ['/usr/bin/apt', '-qq', 'list', '--upgradable'] - self.run( + self.execute( args, ignore_stderr=True, output_msg="Found the following package upgrades:", @@ -184,11 +178,39 @@ class UpdateWindow(Gtk.ApplicationWindow): def on_quit(self, *args): self.application.quit() + def update_buffer(self): + try: + text = self.stdout_queue.get(block=False) + self.append_mesg("STDOUT", text) + match = re.fullmatch(r'EXIT (\d+)', text) + if match is not None: + exit_code = int(match.group(1)) + if exit_code != 0: + self.append_mesg( + "ERROR", + "Command exited with code %d" % exit_code) + + self.unlock() + + except queue.Empty: + pass + + try: + text = self.stderr_queue.get(block=False) + self.append_mesg("STDERR", text) + except queue.Empty: + pass + + return True + def __init__(self, application): super(UpdateWindow, self).__init__( application=application, title="Simple APT Update") self.application = application + self.stdout_queue = queue.Queue() + self.stderr_queue = queue.Queue() + GLib.timeout_add(100, self.update_buffer) self.init_ui() def init_ui(self): @@ -282,8 +304,6 @@ class SimpleAptUpdate(Gtk.Application): self.window.present() self.window.show_all() - self.window.update() - self.window.list() def main():