diff --git a/README.md b/README.md index c36d333..cc978ad 100644 --- a/README.md +++ b/README.md @@ -8,12 +8,10 @@ A GUI for basic tasks of package management using apt: * Check for and list all available upgrades * Download and install all available upgrades -![Screenshot](doc/screenshot.png "Screenshot: Updating the Package Cache") - ## Dependencies ```shell -sudo apt install python3-gi +apt install python3-gi ``` ## Installation diff --git a/bin/simple-apt-update b/bin/simple-apt-update index 15d9152..7170658 100755 --- a/bin/simple-apt-update +++ b/bin/simple-apt-update @@ -60,8 +60,9 @@ class UpdateWindow(Gtk.ApplicationWindow): self.buffer.set_text("") def scroll_to_bottom(self): - self.buffer.get_end_iter() - self.text_view.scroll_to_mark(self.text_mark_end, 0, False, 0, 0) + adj = self.scrolledwindow.get_vadjustment() + adj.set_value(adj.get_upper()) + self.scrolledwindow.set_vadjustment(adj) def level_to_color(self, level): if level == "INFO": @@ -71,76 +72,47 @@ class UpdateWindow(Gtk.ApplicationWindow): else: return "grey" + def prepend_mesg(self, level, text): + self.prepend(text) + self.prepend_color(level + ": ", self.level_to_color(level)) + def append_mesg(self, level, text): - self.append_color( - level + ": ", - self.level_to_color(level) - ) + self.append_color(level + ": ", self.level_to_color(level)) self.append(text) + def prepend_markup(self, markup): + self.insert_markup(markup, self.buffer.get_start_iter()) + def append_markup(self, markup): - self.insert_markup( - markup, - self.buffer.get_end_iter() - ) + self.insert_markup(markup, self.buffer.get_end_iter()) def insert_markup(self, markup, iter): self.buffer.insert_markup(iter, markup, -1) + def prepend_color(self, text, color): + self.insert_color(text, color, self.buffer.get_start_iter()) + def append_color(self, text, color): - self.insert_color( - text, - color, - self.buffer.get_end_iter() - ) + self.insert_color(text, color, self.buffer.get_end_iter()) def insert_color(self, text, color, iter): self.buffer.insert_markup( iter, - "%s" % ( - color, - html.escape(text)), + "%s" % (color, html.escape(text)), -1) + def prepend(self, text): + self.insert(text, self.buffer.get_start_iter()) + def append(self, text): - self.insert( - text, - self.buffer.get_end_iter() - ) + self.insert(text, self.buffer.get_end_iter()) self.scroll_to_bottom() def insert(self, text, iter): self.buffer.insert(iter, text + "\n") - def run_thread(self, args, env, prefix=None): - if self.thread is None: - do_run = True - elif not self.thread.is_alive(): - do_run = True - else: - do_run = False - - if do_run: - self.thread = threading.Thread( - target=self.run, - args=(args, env, prefix,)) - - self.thread.start() - - return False - else: - return True - - def execute( - self, - args, - ignore_stderr=False, - output_msg=None, - empty_msg=None, - prefix=None, - env={}, - clear=True - ): + def execute(self, args, ignore_stderr=False, output_msg=None, + empty_msg=None, env={}, clear=True): self.lock() if clear: @@ -151,24 +123,15 @@ class UpdateWindow(Gtk.ApplicationWindow): self.ignore_stderr = ignore_stderr self.stdout = '' self.stderr = '' - self.append_mesg( + self.prepend_mesg( "INFO", "Running command \"%s\" ..." % " ".join(args)) + thread = threading.Thread(target=self.run, args=(args, env,)) + thread.start() - GLib.timeout_add( - 250, - self.run_thread, - args, - env, - prefix - ) - - def run(self, args, env={}, prefix=None): + def run(self, args, env={}): p = subprocess.Popen( - args, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - shell=False, + args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=False, env=dict(os.environ, **env)) sel = selectors.DefaultSelector() @@ -182,10 +145,7 @@ class UpdateWindow(Gtk.ApplicationWindow): if data: if key.fileobj is p.stdout: - if prefix is None: - self.stdout_queue.put(data) - else: - self.stdout_queue.put(prefix + " " + data) + self.stdout_queue.put(data) else: self.stderr_queue.put(data) @@ -216,15 +176,16 @@ class UpdateWindow(Gtk.ApplicationWindow): env=env, empty_msg="No package upgrades were performed.") + def on_upgrade(self, *args): + self.upgrade() + def update(self, clear=True): args = ['/usr/bin/apt-get', '-y', 'update'] env = {'DEBIAN_FRONTEND': 'noninteractive'} - self.execute( - args, - env=env, - clear=clear, - output_msg="The package cache was refreshed." - ) + self.execute(args, env=env, clear=clear) + + def on_update(self, *args): + self.update() def list(self, clear=True): args = ['/usr/bin/apt', '-qq', 'list', '--upgradable'] @@ -232,16 +193,8 @@ class UpdateWindow(Gtk.ApplicationWindow): args, ignore_stderr=True, clear=clear, - prefix="UPDATE", - output_msg="Done listing available upgrades.", - empty_msg="No package upgrades found." - ) - - def on_update(self, *args): - self.update() - - def on_upgrade(self, *args): - self.upgrade() + output_msg="Found the following package upgrades:", + empty_msg="Currently there are no available package upgrades.") def on_list(self, *args): self.list() @@ -249,39 +202,27 @@ class UpdateWindow(Gtk.ApplicationWindow): def on_quit(self, *args): self.application.quit() - def process_exit(self, exit_code, empty_msg=None, output_msg=None): - self.thread.join() - - if exit_code != 0: - self.append_mesg( - "ERROR", - "Command exited with code %d" % exit_code - ) - elif self.stdout == '' and self.empty_msg is not None: - self.append_mesg("INFO", self.empty_msg) - elif self.stdout != '' and self.output_msg is not None: - self.append_mesg("INFO", self.output_msg) - - self.unlock() - def update_buffer(self): try: text = self.stdout_queue.get(block=False) - match = re.fullmatch(r'(EXIT|UPDATE) (.*)', text) + match = re.fullmatch(r'EXIT (\d+)', text) if match is None: self.stdout += text self.append_mesg("STDOUT", text) - elif match.group(1) == 'EXIT': - exit_code = int(match.group(2)) - self.process_exit(exit_code) - elif match.group(1) == 'UPDATE': - text = match.group(2) - self.stdout += text - self.append_mesg("UPDATE", text) else: - self.stdout += text - self.append_mesg("UPDATE", text) + exit_code = int(match.group(1)) + + if exit_code != 0: + self.append_mesg( + "ERROR", + "Command exited with code %d" % exit_code) + elif self.stdout == '' and self.empty_msg is not None: + self.append_mesg("INFO", self.empty_msg) + elif self.stdout != '' and self.output_msg is not None: + self.append_mesg("INFO", self.empty_msg) + + self.unlock() except queue.Empty: pass @@ -299,31 +240,20 @@ class UpdateWindow(Gtk.ApplicationWindow): return True def __init__(self, application): - super( - UpdateWindow, - self - ).__init__( + super(UpdateWindow, self).__init__( application=application, - title="Simple APT Update" - ) - + title="Simple APT Update") self.application = application - self.thread = None self.stdout_queue = queue.Queue() self.stderr_queue = queue.Queue() - - self.init_ui() - GLib.timeout_add(100, self.update_buffer) + self.init_ui() def init_ui(self): self.set_border_width(10) self.set_default_size(630, 390) - hbox = Gtk.Box( - spacing=6, - orientation=Gtk.Orientation.VERTICAL - ) + hbox = Gtk.Box(spacing=6, orientation=Gtk.Orientation.VERTICAL) self.add(hbox) grid = Gtk.Grid() @@ -342,12 +272,9 @@ class UpdateWindow(Gtk.ApplicationWindow): grid.attach(self.list_button, 1, 0, 1, 1) self.upgrade_button = Gtk.Button.new_from_icon_name( - "gtk-apply", - Gtk.IconSize.BUTTON - ) + "gtk-apply", Gtk.IconSize.BUTTON) self.upgrade_button.set_tooltip_text( - "Download and install all available upgrades" - ) + "Download and install all available upgrades") self.upgrade_button.connect("clicked", self.on_upgrade) grid.attach(self.upgrade_button, 2, 0, 1, 1) @@ -356,9 +283,7 @@ class UpdateWindow(Gtk.ApplicationWindow): grid.attach(self.spinner, 3, 0, 1, 1) self.quit_button = Gtk.Button.new_from_icon_name( - "exit", - Gtk.IconSize.BUTTON - ) + "exit", Gtk.IconSize.BUTTON) self.quit_button.set_tooltip_text("Exit the program") self.quit_button.set_halign(Gtk.Align.END) self.quit_button.connect("clicked", self.on_quit) @@ -371,43 +296,32 @@ class UpdateWindow(Gtk.ApplicationWindow): self.scrolledwindow.set_max_content_height(300) self.buffer = Gtk.TextBuffer() - self.text_view = Gtk.TextView(buffer=self.buffer) - self.text_view.set_editable(False) - self.text_view.set_monospace(True) - self.text_view.set_cursor_visible(False) + text_view = Gtk.TextView(buffer=self.buffer) + text_view.set_editable(False) + text_view.set_monospace(True) + text_view.set_cursor_visible(False) - text_buffer = self.text_view.get_buffer() - iter = text_buffer.get_end_iter() - self.text_mark_end = text_buffer.create_mark("", iter, False) - - self.scrolledwindow.add(self.text_view) + self.scrolledwindow.add(text_view) hbox.pack_start(self.scrolledwindow, True, True, 0) class SimpleAptUpdate(Gtk.Application): def __init__(self): - super( - SimpleAptUpdate, - self - ).__init__( - application_id='de.linuxfoo.SimpleAptUpdate', - flags=Gio.ApplicationFlags.FLAGS_NONE - ) - + super().__init__(application_id='de.linuxfoo.SimpleAptUpdate', + flags=Gio.ApplicationFlags.FLAGS_NONE) self.connect('activate', self.on_activate) - signal.signal(signal.SIGINT, signal.SIG_DFL) + def do_command_line(self, cmdline): + pass + def on_activate(self, application): self.window = UpdateWindow(application) action = Gio.SimpleAction.new("quit", None) action.connect("activate", self.window.on_quit) self.add_action(action) - self.set_accels_for_action( - 'app.quit', - ['q', 'w'] - ) + self.set_accels_for_action('app.quit', ['q', 'w']) action = Gio.SimpleAction.new("update", None) action.connect("activate", self.window.on_update) @@ -426,6 +340,7 @@ class SimpleAptUpdate(Gtk.Application): self.window.present() self.window.show_all() + self.window.update() self.window.list(clear=False) @@ -440,5 +355,3 @@ def main(): if __name__ == '__main__': main() - -# vim:fenc=utf-8:et:ts=4:sw=4 diff --git a/doc/screenshot.png b/doc/screenshot.png deleted file mode 100644 index 78d3b84..0000000 Binary files a/doc/screenshot.png and /dev/null differ diff --git a/res/icons/simple-apt-update.svg b/res/icons/simple-apt-update.svg index ebcd35d..5d60243 100644 --- a/res/icons/simple-apt-update.svg +++ b/res/icons/simple-apt-update.svg @@ -7,14 +7,19 @@ xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="31.72216mm" height="36.307823mm" viewBox="0 0 31.72216 36.307823" version="1.1" - id="svg8"> + id="svg8" + inkscape:version="1.0.2 (e86c870879, 2021-01-15)" + sodipodi:docname="simple-apt-update.svg"> + orient="auto" + inkscape:stockid="Arrow2Lend" + inkscape:isstock="true"> + + @@ -127,6 +170,8 @@ + ry="2.9184699" + inkscape:transform-center-x="13.051673" + inkscape:transform-center-y="11.996269" /> + id="path1321" + sodipodi:nodetypes="cc" /> + id="path1869" + sodipodi:nodetypes="ccccc" />