diff --git a/src/host_info.py b/src/host_info.py index 68c497d..66da716 100644 --- a/src/host_info.py +++ b/src/host_info.py @@ -8,7 +8,6 @@ icon_theme.add_search_path(f"{home}/.local/share/flatpak/exports/share/icons") direction = Gtk.Image().get_direction() class Flatpak: - cli_info = None def open_data(self): if not os.path.exists(self.data_path): @@ -18,24 +17,52 @@ class Flatpak: except GLib.GError as e: return e + def get_data_size(self, callback=None): + size = [None] + def thread(*args): + sed = "sed 's/K/ KB/; s/M/ MB/; s/G/ GB/; s/T/ TB/; s/P/ PB/;'" + size[0] = subprocess.run(['sh', '-c', f"du -sh {self.data_path} | {sed}"], capture_output=True, text=True).stdout.split("\t")[0] + def on_done(*arg): + if callback: + callback(f"~ {size[0]}") + Gio.Task.new(None, None, on_done).run_in_thread(thread) + + def trash_data(self, callback=None): + def thread(*args): + subprocess.run(['gio', 'trash', f"{self.data_path}"]) + Gio.Task.new(None, None, lambda *_: callback()).run_in_thread(thread) + def get_cli_info(self): - if not self.cli_info: - cmd = "LC_ALL=C flatpak info " - installation = self.info["installation"] + cli_info = {} + cmd = "LC_ALL=C flatpak info " + installation = self.info["installation"] - if installation == "user": - cmd += "--user " - elif installation == "system": - cmd += "--system " - else: - cmd += f"--installation={installation} " + if installation == "user": + cmd += "--user " + elif installation == "system": + cmd += "--system " + else: + cmd += f"--installation={installation} " - cmd += self.info["ref"] + cmd += self.info["ref"] - output = subprocess.run(['flatpak-spawn', '--host', 'sh', '-c', cmd], text=True, capture_output=True) - print(output) + try: + output = subprocess.run(['flatpak-spawn', '--host', 'sh', '-c', cmd], text=True, capture_output=True).stdout + except Exception as e: + raise e - return self.cli_info + lines = output.strip().split("\n") + for i, word in enumerate(lines): + word = word.strip().split(": ", 1) + if len(word) < 2: + continue + + word[0] = word[0].lower() + if "installed" in word[0]: + word[1] = word[1].replace("?", " ") + cli_info[word[0]] = word[1] + + return cli_info def __init__(self, columns): self.is_runtime = "runtime" in columns[12] @@ -52,6 +79,7 @@ class Flatpak: "options": columns[12], } self.data_path = f"{home}/.var/app/{columns[2]}" + self.data_size = -1 installation = columns[7] if len(i := installation.split(' ')) > 1: self.info["installation"] = i[1].replace("(", "").replace(")", "") diff --git a/src/properties_page/properties_page.blp b/src/properties_page/properties_page.blp index a99dfcc..f842d20 100644 --- a/src/properties_page/properties_page.blp +++ b/src/properties_page/properties_page.blp @@ -6,7 +6,8 @@ template $PropertiesPage : Adw.NavigationPage { Adw.ToastOverlay toast_overlay { Adw.ToolbarView { [top] - Adw.HeaderBar { + Adw.HeaderBar header_bar { + show-title: false; [end] MenuButton more_menu { icon-name: "view-more-symbolic"; @@ -34,7 +35,7 @@ template $PropertiesPage : Adw.NavigationPage { ; } } - ScrolledWindow { + ScrolledWindow scrolled_window { Adw.Clamp { Box { margin-start: 12; @@ -46,6 +47,7 @@ template $PropertiesPage : Adw.NavigationPage { Image app_icon { pixel-size: 100; + margin-top: 6; margin-bottom: 18; icon-name: "application-x-executable-symbolic"; styles["icon-dropshadow"] @@ -68,7 +70,6 @@ template $PropertiesPage : Adw.NavigationPage { wrap: true; wrap-mode: word_char; justify: center; - margin-bottom: 18; margin-start: 6; margin-end: 6; } @@ -76,6 +77,7 @@ template $PropertiesPage : Adw.NavigationPage { Box { spacing: 6; homogeneous: true; + margin-top: 18; margin-bottom: 12; halign: center; Button open_app_button { @@ -95,8 +97,8 @@ template $PropertiesPage : Adw.NavigationPage { Adw.PreferencesGroup actions { margin-bottom: 12; Adw.SwitchRow pin_row { - title: _("Remove When Unused"); - subtitle: _("Pin this runtime so it's never auto removed"); + title: _("Disable Automactic Removal"); + subtitle: _("Pin this runtime to keep it installed"); } Adw.ActionRow data_row { title: _("User Data"); @@ -117,16 +119,27 @@ template $PropertiesPage : Adw.NavigationPage { icon-name: "user-trash-symbolic"; tooltip-text: _("Trash User Data"); } + + [suffix] + Spinner data_spinner { + spinning: true; + } } Adw.ExpanderRow version_row { title: _("Version"); styles ["property"] + [suffix] + Label mask_label { + label: _("Updates disabled"); + styles["warning"] + } Adw.SwitchRow mask_row { title: _("Disable Updates"); subtitle: _("Mask this package so it's never updated"); } Adw.ActionRow downgrade_row { title: _("Change Version"); + subtitle: _("Upgrade or downgrade this package"); activatable: true; Image { icon-name: "right-large-symbolic"; diff --git a/src/properties_page/properties_page.py b/src/properties_page/properties_page.py index b7836f0..060dc4d 100644 --- a/src/properties_page/properties_page.py +++ b/src/properties_page/properties_page.py @@ -1,11 +1,14 @@ from gi.repository import Adw, Gtk#, GLib, Gio, Pango from .error_toast import ErrorToast +import subprocess, os @Gtk.Template(resource_path="/io/github/flattool/Warehouse/properties_page/properties_page.ui") class PropertiesPage(Adw.NavigationPage): __gtype_name__ = 'PropertiesPage' gtc = Gtk.Template.Child toast_overlay = gtc() + header_bar = gtc() + scrolled_window = gtc() app_icon = gtc() name = gtc() description = gtc() @@ -16,6 +19,7 @@ class PropertiesPage(Adw.NavigationPage): data_row = gtc() open_data_button = gtc() trash_data_button = gtc() + data_spinner = gtc() version_row = gtc() mask_row = gtc() downgrade_row = gtc() @@ -40,13 +44,14 @@ class PropertiesPage(Adw.NavigationPage): package = None - def set_properties(self, package): - if package == self.package: + def set_properties(self, package, refresh=False): + if package == self.package and not refresh: # Do not update the ui if the same app row is clicked + print("skip") return self.package = package - self.set_title(package.info["id"]) + self.set_title(_("{} Properties").format(package.info["name"])) self.name.set_label(package.info["name"]) pkg_description = package.info["description"] self.description.set_visible(pkg_description != "") @@ -57,17 +62,94 @@ class PropertiesPage(Adw.NavigationPage): else: self.app_icon.set_from_icon_name("application-x-executable-symbolic") - package.get_cli_info() + self.pin_row.set_visible(package.is_runtime) + self.open_app_button.set_visible(package.is_runtime) + self.open_app_button.set_visible(not package.is_runtime) + if not package.is_runtime: + has_path = os.path.exists(package.data_path) + self.trash_data_button.set_sensitive(has_path) + self.open_data_button.set_sensitive(has_path) + + if has_path: + self.trash_data_button.set_visible(False) + self.open_data_button.set_visible(False) + self.data_spinner.set_visible(True) + self.data_row.set_subtitle(_("Loading User Data")) + + def callback(size): + self.trash_data_button.set_visible(True) + self.open_data_button.set_visible(True) + self.data_spinner.set_visible(False) + self.data_row.set_subtitle(size) + + self.package.get_data_size(lambda size: callback(size)) + else: + self.data_row.set_subtitle(_("No User Data")) + + cli_info = None + try: + cli_info = package.get_cli_info() + except Exception as e: + self.toast_overlay.add_toast(ErrorToast(_("Could not get properties"), str(e), self.main_window).toast) + return + + for key, row in self.info_rows.items(): + row.set_visible(False) + + try: + subtitle = cli_info[key] + row.set_subtitle(subtitle) + row.set_visible(True) + except KeyError: + if key == "version": + row.set_visible(True) + row.set_subtitle(_("No version information found")) + continue + except Exception as e: + self.toast_overlay.add_toast(ErrorToast(_("Could not get properties"), str(e), self.main_window).toast) + continue def open_data_handler(self, *args): if error := self.package.open_data(): self.toast_overlay.add_toast(ErrorToast(_("Could not open data"), str(error), self.main_window).toast) + def trash_data_handler(self, *args): + def when_done(*args): + self.set_properties(self.package, refresh=True) + self.toast_overlay.add_toast(Adw.Toast.new("Trashed User Data")) + try: + self.package.trash_data(when_done) + except Exception as e: + self.toast_overlay.add_toast(ErrorToast(_("Could not trash data"), str(e), self.main_window).toast) + def __init__(self, main_window, **kwargs): super().__init__(**kwargs) # Extra Object Creation self.main_window = main_window + self.info_rows = { + "version": self.version_row, + "installed": self.installed_size_row, + + "id": self.id_row, + "ref": self.ref_row, + "arch": self.arch_row, + "branch": self.branch_row, + "license": self.license_row, + + "runtime": self.runtime_row, + "sdk": self.sdk_row, + "origin": self.origin_row, + "collection": self.collection_row, + "installation": self.installation_row, + + "commit": self.commit_row, + "parent": self.parent_row, + "subject": self.subject_row, + "date": self.date_row, + } # Connections - self.open_data_button.connect("clicked", self.open_data_handler) \ No newline at end of file + self.open_data_button.connect("clicked", self.open_data_handler) + self.scrolled_window.get_vadjustment().connect("value-changed", lambda adjustment: self.header_bar.set_show_title(not adjustment.get_value() == 0)) + self.trash_data_button.connect("clicked", self.trash_data_handler) \ No newline at end of file diff --git a/src/widgets/error_toast.py b/src/widgets/error_toast.py index 8d94509..56663ec 100644 --- a/src/widgets/error_toast.py +++ b/src/widgets/error_toast.py @@ -2,7 +2,7 @@ from gi.repository import Adw, Gtk, Gdk, GLib, Pango clipboard = Gdk.Display.get_default().get_clipboard() class ErrorToast: - def __init__(self, display_msg, error_msg, parent_window, format=True): + def __init__(self, display_msg, error_msg, parent_window): def on_response(dialog, response_id): if response_id == "copy": @@ -10,7 +10,7 @@ class ErrorToast: # Extra Object Creation self.toast = Adw.Toast(title=display_msg, button_label=_("Details")) - popup = Adw.AlertDialog.new(display_msg, error_msg) + popup = Adw.AlertDialog.new(display_msg) # Apply print(display_msg)