From 02ba1cca56aa1dd5f2759519c09aaddd10c179ad Mon Sep 17 00:00:00 2001 From: GloriousEggroll Date: Sat, 12 Apr 2025 19:40:32 -0600 Subject: [PATCH] bump readme --- README.md | 6 ++-- fp_turbo.py | 82 +++++++++++++++++++++++++++++++++++++++----------- main.py | 86 +++++++++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 150 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index ecb2265..49d84d3 100644 --- a/README.md +++ b/README.md @@ -43,9 +43,9 @@ DONE: - Add Package information page/section. - Cleanup permissions GUI - Add permissions override viewing inside per-app permissions view. +- Add 'update all' functionality. TODO: -- Update management GUI (individual apps can already be updated) - add about section - General GUI layout/theming improvements @@ -83,6 +83,7 @@ options: --install APP_ID Install a Flatpak package --remove APP_ID Remove a Flatpak package --update APP_ID Update a Flatpak package + --update-all Apply all available updates --system Install as system instead of user --refresh Install as system instead of user --refresh-local Install as system instead of user @@ -165,6 +166,8 @@ Common CLI combinations: ./fp_turbo.py --remove --system ./fp_turbo.py --update ./fp_turbo.py --update --system +./fp_turbo.py --update-all +./fp_turbo.py --update-all --system ./fp_turbo.py --id --list-file-perms ./fp_turbo.py --id --add-file-perms ./fp_turbo.py --id --remove-file-perms @@ -203,7 +206,6 @@ Common CLI combinations: ./fp_turbo.py --override --global-list-other-perm-values --system ./fp_turbo.py --override --global-add-other-perm-values --perm-value --system ./fp_turbo.py --override --global-remove-other-perm-values --perm-value --system - ./fp_turbo.py --get-all-portal-permissions ./fp_turbo.py --get-portal-permissions ./fp_turbo.py --get-app-portal-permissions --id diff --git a/fp_turbo.py b/fp_turbo.py index 60ae478..abc2e80 100755 --- a/fp_turbo.py +++ b/fp_turbo.py @@ -720,10 +720,11 @@ class AppstreamSearcher: self.installed_results.extend(search_result) elif "updates" in category: updates = searcher.check_updates(system) - for repo_name, app_id, repo_type in updates: + for app_id, repo_name, repo_type in updates: if repo_name: search_result = searcher.search_flatpak(app_id, repo_name) self.updates_results.extend(search_result) + # Update progress bar self.refresh_progress = (current_category / total_categories) * 100 # make sure to reset these to empty before refreshing. @@ -897,9 +898,11 @@ def install_flatpak(app: AppStreamPackage, repo_name=None, system=False) -> tupl installation = get_installation(system) transaction = Flatpak.Transaction.new_for_installation(installation) - - # Add the install operation - transaction.add_install(repo_name, app.flatpak_bundle, None) + available_apps = installation.list_remote_refs_sync(repo_name) + for available_app in available_apps: + if app.id in available_app.get_name(): + # Add the install operation + transaction.add_install(repo_name, available_app.format_ref(), None) # Run the transaction try: transaction.run() @@ -942,7 +945,7 @@ def install_flatpakref(ref_file, system=False): return True, f"Successfully installed {ref_file}" -def remove_flatpak(app: AppStreamPackage, repo_name=None, system=False) -> tuple[bool, str]: +def remove_flatpak(app: AppStreamPackage, system=False) -> tuple[bool, str]: """ Remove a Flatpak package using transactions. @@ -953,15 +956,15 @@ def remove_flatpak(app: AppStreamPackage, repo_name=None, system=False) -> tuple Returns: Tuple[bool, str]: (success, message) """ - if not repo_name: - repo_name = "flathub" # Get the appropriate installation based on user parameter installation = get_installation(system) - + installed = installation.list_installed_refs(None) # Create a new transaction for removal transaction = Flatpak.Transaction.new_for_installation(installation) - transaction.add_uninstall(app.flatpak_bundle) + for installed_ref in installed: + if app.id in installed_ref.get_name(): + transaction.add_uninstall(installed_ref.format_ref()) # Run the transaction try: transaction.run() @@ -969,7 +972,7 @@ def remove_flatpak(app: AppStreamPackage, repo_name=None, system=False) -> tuple return False, f"Failed to remove {app.id}: {e}" return True, f"Successfully removed {app.id}" -def update_flatpak(app: AppStreamPackage, repo_name=None, system=False) -> tuple[bool, str]: +def update_flatpak(app: AppStreamPackage, system=False) -> tuple[bool, str]: """ Remove a Flatpak package using transactions. @@ -980,15 +983,16 @@ def update_flatpak(app: AppStreamPackage, repo_name=None, system=False) -> tuple Returns: Tuple[bool, str]: (success, message) """ - if not repo_name: - repo_name = "flathub" # Get the appropriate installation based on user parameter installation = get_installation(system) - + updates = installation.list_installed_refs_for_update(None) # Create a new transaction for removal transaction = Flatpak.Transaction.new_for_installation(installation) - transaction.add_update(app.flatpak_bundle) + + for update in updates: + if app.id == update.get_name(): + transaction.add_update(update.format_ref()) # Run the transaction try: transaction.run() @@ -996,6 +1000,31 @@ def update_flatpak(app: AppStreamPackage, repo_name=None, system=False) -> tuple return False, f"Failed to update {app.id}: {e}" return True, f"Successfully updated {app.id}" +def update_all_flatpaks(apps: list[AppStreamPackage], system=False) -> tuple[bool, str]: + """ + Update multiple Flatpak packages using transactions. + + Args: + apps (Union[List[AppStreamPackage], AppStreamPackage]): One or more packages to update + system (Optional[bool]): Whether to operate on user or system installation + + Returns: + Tuple[bool, List[str]]: (success, list of status messages) + """ + + installation = get_installation(system) + updates = installation.list_installed_refs_for_update(None) + # Create a new transaction for removal + transaction = Flatpak.Transaction.new_for_installation(installation) + for update in updates: + transaction.add_update(update.format_ref()) + + try: + transaction.run() + return True, "Successfully updated all packages" + except GLib.Error as e: + return False, f"Failed to update all packages: {str(e)}" + def get_installation(system=False): if system is False: installation = Flatpak.Installation.new_user() @@ -2253,6 +2282,8 @@ def main(): help='Remove a Flatpak package') parser.add_argument('--update', type=str, metavar='APP_ID', help='Update a Flatpak package') + parser.add_argument('--update-all', action='store_true', + help='Update all Flatpak packages') parser.add_argument('--system', action='store_true', help='Install as system instead of user') parser.add_argument('--refresh', action='store_true', help='Install as system instead of user') parser.add_argument('--refresh-local', action='store_true', help='Install as system instead of user') @@ -2340,6 +2371,10 @@ def main(): handle_update(args, searcher) return + if args.update_all: + handle_update_all(args, searcher) + return + # Handle information operations if args.list_installed: handle_list_installed(args, searcher) @@ -2504,7 +2539,7 @@ def handle_remove(args, searcher): result_message = "" for package in packagelist: try: - success, message = remove_flatpak(package, args.repo, args.system) + success, message = remove_flatpak(package, args.system) result_message = f"{message}" break except GLib.Error as e: @@ -2513,11 +2548,11 @@ def handle_remove(args, searcher): print(result_message) def handle_update(args, searcher): - packagelist = searcher.search_flatpak(args.update, args.repo) + packagelist = searcher.search_flatpak(args.update) result_message = "" for package in packagelist: try: - success, message = update_flatpak(package, args.repo, args.system) + success, message = update_flatpak(package, args.system) result_message = f"{message}" break except GLib.Error as e: @@ -2525,6 +2560,19 @@ def handle_update(args, searcher): pass print(result_message) +def handle_update_all(args, searcher): + packagelist = searcher.search_flatpak(args.update) + result_message = "" + for package in packagelist: + try: + success, message = update_all_flatpaks(package, args.system) + result_message = f"{message}" + break + except GLib.Error as e: + result_message = f"Unable to apply updates: {str(e)}" + pass + print(result_message) + def handle_list_installed(args, searcher): installed_apps = searcher.get_installed_apps(args.system) print(f"\nInstalled Flatpak Applications ({len(installed_apps)}):") diff --git a/main.py b/main.py index 44b0cce..3c4b4b7 100755 --- a/main.py +++ b/main.py @@ -295,6 +295,10 @@ class MainWindow(Gtk.Window): .app-type-label { font-size: 0.8em; } + .updates_available_bar { + background-color: #18A3FF; + padding: 4px; + } .screenshot-bullet { color: #18A3FF; font-size: 30px; @@ -917,6 +921,7 @@ class MainWindow(Gtk.Window): self.current_group = group self.update_category_header(category) self.update_subcategories_bar(category) + self.update_updates_available_bar(category) self.show_category_apps(category) @@ -969,6 +974,15 @@ class MainWindow(Gtk.Window): self.subcategories_bar.set_visible(False) self.subcategories_bar.set_halign(Gtk.Align.FILL) # Ensure full width self.right_panel.pack_start(self.subcategories_bar, False, False, 0) + + # Create subcategories bar (initially hidden) + self.updates_available_bar = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) + self.updates_available_bar.set_hexpand(True) + self.updates_available_bar.set_spacing(6) + self.updates_available_bar.set_border_width(6) + self.updates_available_bar.set_visible(False) + self.updates_available_bar.set_halign(Gtk.Align.FILL) # Ensure full width + self.right_panel.pack_start(self.updates_available_bar, False, False, 0) self.right_panel.pack_start(Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL), False, False, 0) # Create scrollable area @@ -1150,6 +1164,68 @@ class MainWindow(Gtk.Window): self.subcategories_bar.queue_resize() self.subcategories_bar.show_all() + def update_updates_available_bar(self, category): + for child in self.updates_available_bar.get_children(): + child.destroy() + + if category == "updates": + if self.updates_results != [] : + self.updates_available_bar.get_style_context().add_class("updates_available_bar") + self.updates_available_bar.set_visible(True) + + buttons_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) + buttons_box.set_spacing(6) + buttons_box.set_margin_top(4) + buttons_box.set_halign(Gtk.Align.END) + + update_all_button = Gtk.Button() + update_all_icon = Gio.Icon.new_for_string('system-software-update-symbolic') + update_all_button.set_image(Gtk.Image.new_from_gicon(update_all_icon, Gtk.IconSize.BUTTON)) + update_all_button.connect("clicked", self.on_update_all_button_clicked) + buttons_box.pack_end(update_all_button, False, False, 0) + + # Create left label + left_label = Gtk.Label(label="Update All: ") + left_label.set_halign(Gtk.Align.END) # Align left + self.updates_available_bar.pack_end(buttons_box, False, False, 0) + self.updates_available_bar.pack_end(left_label, False, False, 0) + + self.updates_available_bar.show_all() + else: + self.updates_available_bar.set_visible(False) + + def on_update_all_button_clicked(self, button=None): + # Create a message dialog + dialog = Gtk.MessageDialog( + transient_for=self, # Parent window + modal=True, # Make it modal + message_type=Gtk.MessageType.QUESTION, + buttons=Gtk.ButtonsType.OK_CANCEL, + text="Download and install all available Flatpak updates?", + title="Confirm" + ) + + # Show the dialog and get the response + response = dialog.run() + + # Handle the response + if response == Gtk.ResponseType.OK: + # Perform Removal + def perform_update(): + # Show waiting dialog + GLib.idle_add(self.show_waiting_dialog, "Updating packages...") + + success, message = fp_turbo.update_all_flatpaks(self.updates_results, self.system_mode) + + # Update UI on main thread + GLib.idle_add(lambda: self.on_task_complete(dialog, success, message)) + + # Start spinner and begin installation + thread = threading.Thread(target=perform_update) + thread.daemon = True # Allow program to exit even if thread is still running + thread.start() + dialog.destroy() + def get_parent_category(self, subcategory): for parent, subcats in self.subcategory_groups.items(): if subcategory in subcats: @@ -1507,14 +1583,14 @@ class MainWindow(Gtk.Window): # Add repository labels repo_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) repo_box.set_spacing(4) - repo_box.set_halign(Gtk.Align.END) - repo_box.set_valign(Gtk.Align.END) + repo_box.set_halign(Gtk.Align.START) + repo_box.set_valign(Gtk.Align.START) # Add repository labels for repo in sorted(app_data['repos']): repo_label = Gtk.Label(label=f"{repo}") #repo_label.get_style_context().add_class("app-repo-label") - repo_label.set_halign(Gtk.Align.END) + repo_label.set_halign(Gtk.Align.START) repo_box.pack_end(repo_label, False, False, 0) repo_list_label = Gtk.Label(label="Sources: ") repo_box.pack_end(repo_list_label, False, False, 0) @@ -1818,7 +1894,7 @@ class MainWindow(Gtk.Window): # Show waiting dialog GLib.idle_add(self.show_waiting_dialog, "Removing package...") - success, message = fp_turbo.remove_flatpak(app, None, self.system_mode) + success, message = fp_turbo.remove_flatpak(app, self.system_mode) # Update UI on main thread GLib.idle_add(lambda: self.on_task_complete(dialog, success, message)) @@ -3543,7 +3619,7 @@ class MainWindow(Gtk.Window): # Show waiting dialog GLib.idle_add(self.show_waiting_dialog, "Updating package...") - success, message = fp_turbo.update_flatpak(app, None, self.system_mode) + success, message = fp_turbo.update_flatpak(app, self.system_mode) # Update UI on main thread GLib.idle_add(lambda: self.on_task_complete(dialog, success, message))