From 342c2d4194f46e34eca777a4e0cddfa29deb5b89 Mon Sep 17 00:00:00 2001 From: heliguy Date: Wed, 3 Jul 2024 16:35:39 -0400 Subject: [PATCH] Fresh start Remove pretty much everything in preparation of complete overhaul for 2.X --- data/ui/downgrade.blp | 84 --- data/ui/filter.blp | 83 --- data/ui/orphans.blp | 135 ----- data/ui/popular_remotes.blp | 59 --- data/ui/properties.blp | 164 ------ data/ui/remotes.blp | 95 ---- data/ui/search_install.blp | 193 ------- data/ui/snapshots.blp | 90 ---- data/ui/window.blp | 299 ----------- po/POTFILES | 21 +- po/warehouse.pot | 923 +-------------------------------- src/app_row_widget.py | 381 -------------- src/common.py | 527 ------------------- src/downgrade_window.py | 168 ------ src/filter_window.py | 226 -------- src/main.py | 14 - src/main_window/window.blp | 73 +++ src/main_window/window.py | 64 +++ src/meson.build | 36 +- src/orphans_window.py | 353 ------------- src/properties_window.py | 217 -------- src/remotes_window.py | 710 ------------------------- src/search_install_window.py | 266 ---------- src/snapshots_window.py | 281 ---------- src/warehouse.gresource.xml | 9 +- src/warehouse.in | 2 +- src/window.py | 970 ----------------------------------- 27 files changed, 171 insertions(+), 6272 deletions(-) delete mode 100644 data/ui/downgrade.blp delete mode 100644 data/ui/filter.blp delete mode 100644 data/ui/orphans.blp delete mode 100644 data/ui/popular_remotes.blp delete mode 100644 data/ui/properties.blp delete mode 100644 data/ui/remotes.blp delete mode 100644 data/ui/search_install.blp delete mode 100644 data/ui/snapshots.blp delete mode 100644 data/ui/window.blp delete mode 100644 src/app_row_widget.py delete mode 100644 src/common.py delete mode 100644 src/downgrade_window.py delete mode 100644 src/filter_window.py create mode 100644 src/main_window/window.blp create mode 100644 src/main_window/window.py delete mode 100644 src/orphans_window.py delete mode 100644 src/properties_window.py delete mode 100644 src/remotes_window.py delete mode 100644 src/search_install_window.py delete mode 100644 src/snapshots_window.py delete mode 100644 src/window.py diff --git a/data/ui/downgrade.blp b/data/ui/downgrade.blp deleted file mode 100644 index 9454a82..0000000 --- a/data/ui/downgrade.blp +++ /dev/null @@ -1,84 +0,0 @@ -using Gtk 4.0; -using Adw 1; - -template $DowngradeWindow: Adw.Dialog { - content-width: 500; - content-height: 450; - - Adw.ToolbarView main_toolbar_view { - [top] - Adw.HeaderBar header_bar { - } - - [bottom] - ActionBar action_bar { - revealed: false; - - [center] - Button apply_button { - visible: false; - valign: end; - halign: center; - margin-top: 6; - margin-bottom: 6; - Adw.ButtonContent { - label: _("Downgrade"); - icon-name: "arrow-turn-left-down-symbolic"; - } - - styles [ - "suggested-action", - "pill" - ] - } - } - - content: Adw.ToastOverlay toast_overlay { - Stack main_stack { - Box loading { - orientation: vertical; - spacing: 10; - margin-top: 40; - margin-bottom: 20; - halign: center; - valign: center; - - Spinner { - margin-bottom: 35; - width-request: 30; - height-request: 30; - opacity: 0.5; - spinning: true; - } - - Label loading_label { - label: _("Fetching Releases"); - styles [ - "title-1", - "title" - ] - } - - Label { - label: _("This could take a while"); - styles ["description", "body"] - } - } - - Adw.PreferencesPage outerbox { - Adw.PreferencesGroup { - Adw.SwitchRow mask_row { - title: _("Disable Updates"); - active: true; - } - } - - Adw.PreferencesGroup versions_group { - title: _("Select a Release"); - description: _("This will uninstall the current release and install the chosen one instead. Note that downgrading can cause issues."); - } - } - } - }; - } -} diff --git a/data/ui/filter.blp b/data/ui/filter.blp deleted file mode 100644 index 093ec0c..0000000 --- a/data/ui/filter.blp +++ /dev/null @@ -1,83 +0,0 @@ -using Gtk 4.0; -using Adw 1; - -template $FilterWindow: Adw.Dialog { - title: _("Set Filters"); - content-width: 500; - content-height: 450; - - child: - Adw.ToolbarView main_toolbar_view { - [top] - Adw.HeaderBar header_bar { - } - - content: Adw.ToastOverlay toast_overlay { - Stack main_stack { - Overlay main_overlay { - ScrolledWindow scrolled_window { - vexpand: true; - - Adw.Clamp { - Box outerbox { - orientation: vertical; - - ListBox install_type_list { - margin-top: 12; - margin-bottom: 12; - margin-start: 12; - margin-end: 12; - hexpand: true; - valign: start; - selection-mode: none; - - styles [ - "boxed-list" - ] - - Adw.ActionRow apps_row { - title: _("Show Apps"); - - Switch show_apps_switch { - valign: center; - } - - activatable-widget: show_apps_switch; - } - - Adw.ActionRow show_runtimes_row { - title: _("Show Runtimes"); - - Switch show_runtimes_switch { - valign: center; - } - - activatable-widget: show_runtimes_switch; - } - - Adw.ExpanderRow remotes_expander { - enable-expansion: true; - title: _("Filter by Remotes"); - } - - Adw.ExpanderRow runtimes_expander { - enable-expansion: true; - title: _("Filter by Runtimes"); - } - } - - Button reset_button { - visible: true; - margin-bottom: 18; - halign: center; - label: _("Reset Filters"); - styles ["pill"] - } - } - } - } - } - } - }; - }; -} diff --git a/data/ui/orphans.blp b/data/ui/orphans.blp deleted file mode 100644 index 0bb2b37..0000000 --- a/data/ui/orphans.blp +++ /dev/null @@ -1,135 +0,0 @@ -using Gtk 4.0; -using Adw 1; - -template $OrphansWindow: Adw.Dialog { - content-width: 500; - content-height: 450; - - Adw.ToolbarView main_toolbar_view { - [top] - Adw.HeaderBar header_bar { - [start] - ToggleButton search_button { - icon-name: "system-search-symbolic"; - tooltip-text: _("Search List"); - } - - [end] - Button oepn_folder_button { - icon-name: "document-open-symbolic"; - tooltip-text: _("Open Data Folder"); - } - } - - [top] - SearchBar search_bar { - search-mode-enabled: bind search_button.active bidirectional; - key-capture-widget: main_toolbar_view; - - Adw.Clamp { - maximum-size: 577; - hexpand: true; - - SearchEntry search_entry {} - } - } - - content: Adw.ToastOverlay toast_overlay { - Overlay main_overlay { - Stack main_stack { - Box main_box { - orientation: vertical; - - ScrolledWindow scrolled_window { - vexpand: true; - - Adw.Clamp { - ListBox list_of_data { - margin-top: 12; - margin-bottom: 12; - margin-start: 12; - margin-end: 12; - hexpand: true; - valign: start; - selection-mode: none; - - styles [ - "boxed-list" - ] - } - } - } - } - - Box installing { - orientation: vertical; - spacing: 10; - margin-top: 40; - margin-bottom: 20; - halign: center; - valign: center; - - Spinner spinner { - margin-bottom: 35; - width-request: 30; - height-request: 30; - opacity: 0.5; - spinning: true; - } - - Label { - label: _("Installing"); - - styles [ - "title-1", - "title" - ] - } - - Label installing_status { - label: ""; - justify: center; - - styles [ - "description", - "body" - ] - } - } - - Adw.StatusPage no_data { - icon-name: "check-plain-symbolic"; - title: _("No Leftover Data"); - description: _("There is no leftover user data"); - } - - Adw.StatusPage no_results { - icon-name: "system-search-symbolic"; - title: _("No Results Found"); - description: _("Try a different search term"); - } - } - } - }; - - [bottom] - ActionBar action_bar { - [start] - ToggleButton select_all_button { - label: _("Select All"); - } - - [end] - Button trash_button { - label: _("Trash"); - sensitive: false; - } - - [end] - Button install_button { - label: _("Install"); - sensitive: false; - } - } - } -} diff --git a/data/ui/popular_remotes.blp b/data/ui/popular_remotes.blp deleted file mode 100644 index 5625e21..0000000 --- a/data/ui/popular_remotes.blp +++ /dev/null @@ -1,59 +0,0 @@ -using Gtk 4.0; -using Adw 1; - -template $PopularRemotesWindow: Adw.Window { - default-width: 450; - default-height: 530; - title: ""; - - Adw.ToolbarView main_toolbar_view { - [top] - HeaderBar header_bar {} - - content: Adw.ToastOverlay toast_overlay { - vexpand: true; - - Adw.StatusPage { - valign: start; - title: _("Add Remote"); - description: _("Choose from a list of popular remotes or add a new one"); - - Adw.Clamp { - Box { - orientation: vertical; - - ListBox list_of_remotes { - hexpand: true; - valign: start; - selection-mode: none; - - styles [ - "boxed-list" - ] - } - - ListBox custom_list { - hexpand: true; - valign: start; - selection-mode: none; - - styles [ - "boxed-list" - ] - - Adw.ActionRow add_from_file { - title: _("Add a Repo File"); - activatable: true; - } - - Adw.ActionRow custom_remote { - title: _("Add a Custom Remote"); - activatable: true; - } - } - } - } - } - }; - } -} diff --git a/data/ui/properties.blp b/data/ui/properties.blp deleted file mode 100644 index 8d52f58..0000000 --- a/data/ui/properties.blp +++ /dev/null @@ -1,164 +0,0 @@ -using Gtk 4.0; -using Adw 1; - -template $PropertiesWindow: Adw.Dialog { - content-width: 350; - content-height: 999999; - title: _("Properties"); - - Adw.ToolbarView main_toolbar_view { - [top] - Adw.HeaderBar header_bar {} - - content: Adw.ToastOverlay toast_overlay { - Box { - orientation: vertical; - - Adw.Banner eol_app_banner {} - - Adw.Banner eol_runtime_banner {} - - Adw.Banner mask_banner {} - - ScrolledWindow { - Adw.Clamp { - Box { - orientation: vertical; - hexpand: false; - vexpand: true; - spacing: 12; - margin-top: 12; - margin-start: 12; - margin-end: 12; - margin-bottom: 12; - - Image app_icon { - pixel-size: 100; - - styles [ - "icon-dropshadow" - ] - } - - Label name { - wrap: true; - - styles [ - "title-1" - ] - } - - Button description_button { - styles [ - "title-4", - "flat" - ] - - Box { - spacing: 12; - - Label description { - halign: start; - wrap: true; - hexpand: true; - } - Image { - icon-name: "edit-copy-symbolic"; - } - } - } - - Adw.PreferencesGroup upper { - Adw.ActionRow data_row { - title: _("Loading User Data"); - - [suffix] - Button open_data { - icon-name: "document-open-symbolic"; - tooltip-text: _("Open User Data Folder"); - valign: center; - visible: false; - - styles [ - "flat" - ] - } - - [suffix] - Button trash_data { - icon-name: "user-trash-symbolic"; - tooltip-text: _("Trash User Data"); - valign: center; - visible: false; - - styles [ - "flat" - ] - } - - [suffix] - Spinner spinner { - spinning: true; - } - - styles["property"] - } - - Adw.ActionRow view_apps { - title: _("Show Apps Using This Runtime"); - activatable: true; - visible: false; - - [suffix] - Image { - icon-name: "funnel-symbolic"; - } - } - - Adw.ActionRow runtime { - title: _("Runtime"); - - [suffix] - Button runtime_properties { - icon-name: "info-symbolic"; - tooltip-text: _("View Properties"); - valign: center; - - styles [ - "flat" - ] - } - - [suffix] - Button runtime_copy { - icon-name: "edit-copy-symbolic"; - tooltip-text: _("Copy"); - valign: center; - - styles [ - "flat" - ] - } - - styles["property"] - } - - Adw.ActionRow details { - title: _("Show Details in Store"); - activatable: true; - - [suffix] - Image { - icon-name: "adw-external-link-symbolic"; - } - } - } - - Adw.PreferencesGroup lower {} - } - } - } - } - }; - } -} diff --git a/data/ui/remotes.blp b/data/ui/remotes.blp deleted file mode 100644 index ed42b67..0000000 --- a/data/ui/remotes.blp +++ /dev/null @@ -1,95 +0,0 @@ -using Gtk 4.0; -using Adw 1; - -template $RemotesWindow: Adw.Dialog { - title: _("Manage Remotes"); - content-width: 500; - content-height: 450; - - Adw.ToolbarView main_toolbar_view { - [top] - Adw.HeaderBar header_bar { - Button refresh { - icon-name: "view-refresh-symbolic"; - tooltip-text: _("Refresh List of Remotes"); - } - } - - content: Adw.ToastOverlay toast_overlay { - Stack stack { - - Adw.PreferencesPage main_group { - Adw.PreferencesGroup remotes_list { - title: _("Installed Remotes"); - header-suffix: ToggleButton show_disabled_button { - Adw.ButtonContent show_disabled_button_button_content { - icon-name: "eye-not-looking-symbolic"; - label: _("Show Disabled"); - styles["flat"] - } - // spacing: 6; - // margin-end: 6; - // Label { - // label: _("Show Disabled"); - // styles["heading", "h4"] - // } - // Switch show_disabled { - // valign: center; - // } - }; - Adw.ActionRow no_remotes { - title: _("No Remotes Found"); - } - } - Adw.PreferencesGroup popular_remotes_list { - title: _("Add a Popular Remote"); - visible: false; - } - Adw.PreferencesGroup manual_remotes_list { - title: _("Add Other Remotes"); - Adw.ActionRow add_from_file { - title: _("Add a Repo File"); - activatable: true; - } - - Adw.ActionRow custom_remote { - title: _("Add a Custom Remote"); - activatable: true; - } - } - } - - Box adding { - orientation: vertical; - spacing: 10; - margin-top: 40; - margin-bottom: 20; - halign: center; - valign: center; - - Spinner { - margin-bottom: 35; - width-request: 30; - height-request: 30; - opacity: 0.5; - spinning: true; - } - - Label { - label: _("Adding Remote"); - - styles [ - "title-1", - "title" - ] - } - - Label { - label: _("This should only take a moment"); - styles ["description", "body"] - } - } - } - }; - } -} diff --git a/data/ui/search_install.blp b/data/ui/search_install.blp deleted file mode 100644 index 7d06b35..0000000 --- a/data/ui/search_install.blp +++ /dev/null @@ -1,193 +0,0 @@ -using Gtk 4.0; -using Adw 1; - -template $SearchInstallWindow: Adw.Dialog { - content-width: 500; - content-height: 450; - title: ""; - - Adw.ToolbarView main_toolbar_view { - content: - Stack outer_stack { - Adw.NavigationView nav_view { - Adw.NavigationPage search_page { - title: _("Search Criteria"); - Adw.ToastOverlay toast_overlay { - Adw.ToolbarView { - [top] - Adw.HeaderBar { - } - content: - Adw.StatusPage { - title: _("Choose a Remote to Search"); - valign: start; - child: - Adw.Clamp { - ListBox remotes_list { - selection-mode: none; - styles ["boxed-list"] - } - }; - }; - } - } - } - Adw.NavigationPage results_page { - title: _("Results"); - Adw.ToolbarView { - [top] - Adw.HeaderBar { - } - [bottom] - ActionBar action_bar { - revealed: false; - [center] - Button install_button { - margin-top: 6; - margin-bottom: 6; - styles[ - "pill", - "suggested-action" - ] - - Adw.ButtonContent { - label: _("Install"); - icon-name: "plus-large-symbolic"; - } - } - } - [top] - Adw.Clamp { - Box search_box { - margin-top: 4; - margin-start: 12; - margin-end: 12; - margin-bottom: 6; - SearchEntry search_entry { - hexpand: true; - } - Button search_button { - icon-name: "right-large-symbolic"; - tooltip-text: _("Start Search"); - } - styles ["linked"] - } - } - content: - Stack inner_stack { - - Adw.StatusPage blank_page { - title: _("Search for Flatpaks"); - icon-name: "flatpak-symbolic"; - description: _("Search for Flatpaks that you want to install"); - } - - Adw.StatusPage no_results { - icon-name: "system-search-symbolic"; - title: _("No Results Found"); - description: _("Try a different search term"); - } - - Box loading_page { - orientation: vertical; - spacing: 10; - margin-top: 40; - margin-bottom: 20; - halign: center; - valign: center; - - Spinner { - margin-bottom: 35; - width-request: 30; - height-request: 30; - opacity: 0.5; - spinning: true; - } - - Label { - label: _("Searching"); - - styles [ - "title-1", - "title" - ] - } - } - - Adw.StatusPage too_many { - icon-name: "error-symbolic"; - title: _("Too Many Results"); - description: _("Try being more specific with your search"); - } - - ScrolledWindow results_scroll { - vexpand: true; - Adw.Clamp { - ListBox results_list { - margin-top: 6; - margin-bottom: 12; - margin-start: 12; - margin-end: 12; - hexpand: true; - valign: start; - selection-mode: none; - styles ["boxed-list"] - } - } - } - }; - } - } - } - Adw.ToolbarView installing { - [top] - Adw.HeaderBar { - } - content: - Overlay overlay { - [overlay] - ProgressBar progress_bar { - visible: false; - can-target: false; - styles ["osd"] - } - Box { - orientation: vertical; - spacing: 10; - margin-top: 40; - margin-bottom: 20; - halign: center; - valign: center; - - Spinner { - margin-bottom: 35; - width-request: 30; - height-request: 30; - opacity: 0.5; - spinning: true; - } - - Label { - label: _("Installing"); - - styles [ - "title-1", - "title" - ] - } - - Label installing_status { - label: ""; - justify: center; - - styles [ - "description", - "body" - ] - } - } - }; - } - }; - } -} diff --git a/data/ui/snapshots.blp b/data/ui/snapshots.blp deleted file mode 100644 index 6946529..0000000 --- a/data/ui/snapshots.blp +++ /dev/null @@ -1,90 +0,0 @@ -using Gtk 4.0; -using Adw 1; - -template $SnapshotsWindow: Adw.Dialog { - content-width: 500; - content-height: 455; - - Adw.ToolbarView main_toolbar_view { - [top] - Adw.HeaderBar header_bar { - [end] - Button open_folder_button { - icon-name: "document-open-symbolic"; - tooltip-text: _("Open Snapshots Folder"); - } - } - - [bottom] - ActionBar action_bar { - [center] - Button new_snapshot { - halign: center; - sensitive: bind action_bar.revealed; - margin-top: 6; - margin-bottom: 6; - styles[ - "pill", - "suggested-action" - ] - - Adw.ButtonContent { - label: _("New Snapshot"); - icon-name: "plus-large-symbolic"; - } - } - } - - content: Adw.ToastOverlay toast_overlay { - Stack main_stack { - ScrolledWindow outerbox { - Adw.Clamp { - ListBox snapshots_group { - margin-top: 12; - margin-bottom: 12; - margin-start: 12; - margin-end: 12; - valign: start; - selection-mode: none; - - styles [ - "boxed-list" - ] - } - } - } - - Box loading { - orientation: vertical; - spacing: 10; - margin-top: 40; - margin-bottom: 20; - halign: center; - valign: center; - - Spinner { - margin-bottom: 35; - width-request: 30; - height-request: 30; - opacity: 0.5; - spinning: true; - } - - Label loading_label { - styles [ - "title-1", - "title" - ] - } - } - - - Adw.StatusPage no_snapshots { - title: _("No Snapshots"); - description: _("Snapshots are backups of the app's user data. They can be reapplied at any time."); - icon-name: "clock-alt-symbolic"; - } - } - }; - } -} diff --git a/data/ui/window.blp b/data/ui/window.blp deleted file mode 100644 index 591666f..0000000 --- a/data/ui/window.blp +++ /dev/null @@ -1,299 +0,0 @@ -using Gtk 4.0; -using Adw 1; - -template $WarehouseWindow: Adw.ApplicationWindow { - title: "Warehouse"; - Adw.ToolbarView main_toolbar_view { - [top] - HeaderBar header_bar { - [start] - ToggleButton search_button { - icon-name: "system-search-symbolic"; - tooltip-text: _("Search List"); - } - - [start] - Button filter_button { - icon-name: "funnel-symbolic"; - tooltip-text: _("Filter List"); - } - - [end] - MenuButton main_menu { - icon-name: "open-menu-symbolic"; - tooltip-text: _("Main Menu"); - menu-model: primary_menu; - } - - [end] - ToggleButton batch_mode_button { - icon-name: "selection-mode-symbolic"; - tooltip-text: _("Toggle Selection Mode"); - } - } - - [top] - SearchBar search_bar { - search-mode-enabled: bind search_button.active bidirectional; - key-capture-widget: main_toolbar_view; - - Adw.Clamp { - maximum-size: 577; - hexpand: true; - - SearchEntry search_entry {} - } - } - - content: Adw.ToastOverlay toast_overlay { - Overlay main_overlay { - Stack main_stack { - Adw.StatusPage loading_flatpaks { - icon-name: "clock-alt-symbolic"; - title: _("Loading Flatpaks"); - description: _("This should only take a moment"); - } - - Box main_box { - orientation: vertical; - - ScrolledWindow scrolled_window { - vexpand: true; - - Adw.Clamp { - ListBox flatpaks_list_box { - margin-top: 12; - margin-bottom: 12; - margin-start: 12; - margin-end: 12; - hexpand: true; - valign: start; - selection-mode: none; - - styles [ - "boxed-list" - ] - } - } - } - } - - Box installing { - orientation: vertical; - spacing: 10; - margin-top: 40; - margin-bottom: 20; - halign: center; - valign: center; - - Spinner { - margin-bottom: 35; - width-request: 30; - height-request: 30; - opacity: 0.5; - spinning: true; - } - - Label { - label: _("Installing"); - - styles [ - "title-1", - "title" - ] - } - - Label { - label: _("This could take a while"); - styles ["description", "body"] - } - } - - Box uninstalling { - orientation: vertical; - spacing: 10; - margin-top: 40; - margin-bottom: 20; - halign: center; - valign: center; - - Spinner { - margin-bottom: 35; - width-request: 30; - height-request: 30; - opacity: 0.5; - spinning: true; - } - - Label { - label: _("Uninstalling"); - styles ["title-1", "title"] - } - - Label uninstalling_status { - label: ""; - justify: center; - styles ["description", "body"] - } - } - - Box snapshotting { - orientation: vertical; - spacing: 10; - margin-top: 40; - margin-bottom: 20; - halign: center; - valign: center; - - Spinner { - margin-bottom: 35; - width-request: 30; - height-request: 30; - opacity: 0.5; - spinning: true; - } - - Label { - label: _("Creating Snapshots"); - styles ["title-1", "title"] - } - - Label { - label: _("This could take a while"); - styles ["description", "body"] - } - } - - Adw.StatusPage no_flatpaks { - icon-name: "error-symbolic"; - title: _("No Flatpaks Found"); - description: _("Warehouse cannot see the list of installed Flatpaks or the system has no Flatpaks installed"); - } - - Adw.StatusPage no_matches { - icon-name: "funnel-symbolic"; - title: _("No Flatpaks Match Filters"); - description: _("No installed Flatpak matches all of the currently applied filters"); - [child] - Button reset_filters_button { - label: _("Reset Filters"); - halign: center; - styles["pill"] - } - } - - Adw.StatusPage no_results { - icon-name: "system-search-symbolic"; - title: _("No Results Found"); - description: _("Try a different search term"); - } - - Adw.StatusPage refreshing { - icon-name: "arrow-circular-top-right-symbolic"; - title: _("Refreshing List"); - description: _("This should only take a moment"); - } - } - } - }; - - [bottom] - ActionBar batch_mode_bar { - revealed: false; - - [start] - ToggleButton batch_select_all_button { - label: _("Select All"); - } - - [end] - Button batch_uninstall_button { - icon-name: "cross-filled-symbolic"; - tooltip-text: _("Uninstall Selected Apps"); - } - - [end] - Button batch_clean_button { - icon-name: "user-trash-symbolic"; - tooltip-text: _("Send Selected Apps' Data to the Trash"); - } - - [end] - MenuButton batch_copy_button { - icon-name: "edit-copy-symbolic"; - tooltip-text: _("Open Copy Menu"); - menu-model: copy_menu; - } - - [end] - Button batch_snapshot_button { - icon-name: "clock-alt-symbolic"; - tooltip-text: _("Snapshot Selected Apps' Data"); - visible: true; - } - } - } -} - -menu primary_menu { - section { - item { - label: _("Manage Leftover Data…"); - action: "app.manage-data-folders"; - } - - /*item { - label: _("_Preferences"); - action: "app.preferences"; - }*/ - item { - label: _("Manage Remotes…"); - action: "app.show-remotes-window"; - } - } - section { - item { - label: _("Install From File…"); - action: "app.install-from-file"; - } - - item { - label: _("Install From The Web…"); - action: "app.open-search-install"; - } - } - section { - item { - label: _("Refresh List"); - action: "app.refresh-list"; - } - item { - label: _("_Keyboard Shortcuts"); - action: "win.show-help-overlay"; - } - - item { - label: _("_About Warehouse"); - action: "app.about"; - } - } -} - -menu copy_menu { - section { - item { - label: _("Copy Names"); - action: "win.copy-names"; - } - - item { - label: _("Copy IDs"); - action: "win.copy-ids"; - } - - item { - label: _("Copy Refs"); - action: "win.copy-refs"; - } - } -} \ No newline at end of file diff --git a/po/POTFILES b/po/POTFILES index 2fdd683..491b278 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -1,23 +1,8 @@ data/io.github.flattool.Warehouse.desktop.in data/io.github.flattool.Warehouse.metainfo.xml.in data/io.github.flattool.Warehouse.gschema.xml -data/ui/downgrade.blp -data/ui/filter.blp -data/ui/orphans.blp -data/ui/popular_remotes.blp -data/ui/properties.blp -data/ui/remotes.blp -data/ui/search_install.blp -data/ui/snapshots.blp -data/ui/window.blp -src/app_row_widget.py +src/main_window/window.blp +src/main_window/window.py src/common.py -src/downgrade_window.py -src/filter_window.py src/gtk/help-overlay.blp -src/main.py -src/orphans_window.py -src/properties_window.py -src/remotes_window.py -src/snapshots_window.py -src/window.py +src/main.py \ No newline at end of file diff --git a/po/warehouse.pot b/po/warehouse.pot index e49ec7c..8ffd7c8 100644 --- a/po/warehouse.pot +++ b/po/warehouse.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: io.github.flattool.heliguy.Warehouse\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-07-03 14:05-0400\n" +"POT-Creation-Date: 2024-07-03 16:27-0400\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -65,488 +65,44 @@ msgstr "" msgid "Reinstall apps that have leftover data" msgstr "" -#: data/ui/downgrade.blp:25 src/app_row_widget.py:360 -msgid "Downgrade" -msgstr "" - -#: data/ui/downgrade.blp:55 -msgid "Fetching Releases" -msgstr "" - -#: data/ui/downgrade.blp:63 data/ui/window.blp:107 data/ui/window.blp:162 -msgid "This could take a while" -msgstr "" - -#: data/ui/downgrade.blp:71 src/app_row_widget.py:299 src/window.py:494 -msgid "Disable Updates" -msgstr "" - -#: data/ui/downgrade.blp:77 -msgid "Select a Release" -msgstr "" - -#: data/ui/downgrade.blp:78 -msgid "" -"This will uninstall the current release and install the chosen one instead. " -"Note that downgrading can cause issues." -msgstr "" - -#: data/ui/filter.blp:5 src/gtk/help-overlay.blp:19 -msgid "Set Filters" -msgstr "" - -#: data/ui/filter.blp:39 -msgid "Show Apps" -msgstr "" - -#: data/ui/filter.blp:49 -msgid "Show Runtimes" -msgstr "" - -#: data/ui/filter.blp:60 -msgid "Filter by Remotes" -msgstr "" - -#: data/ui/filter.blp:65 -msgid "Filter by Runtimes" -msgstr "" - -#: data/ui/filter.blp:73 data/ui/window.blp:179 -msgid "Reset Filters" -msgstr "" - -#: data/ui/orphans.blp:14 data/ui/window.blp:12 -msgid "Search List" -msgstr "" - -#: data/ui/orphans.blp:20 -msgid "Open Data Folder" -msgstr "" - -#: data/ui/orphans.blp:81 data/ui/search_install.blp:171 data/ui/window.blp:98 -msgid "Installing" -msgstr "" - -#: data/ui/orphans.blp:102 -msgid "No Leftover Data" -msgstr "" - -#: data/ui/orphans.blp:103 -msgid "There is no leftover user data" -msgstr "" - -#: data/ui/orphans.blp:108 data/ui/search_install.blp:87 data/ui/window.blp:187 -msgid "No Results Found" -msgstr "" - -#: data/ui/orphans.blp:109 data/ui/search_install.blp:88 data/ui/window.blp:188 -msgid "Try a different search term" -msgstr "" - -#: data/ui/orphans.blp:119 data/ui/window.blp:206 -msgid "Select All" -msgstr "" - -#: data/ui/orphans.blp:124 src/window.py:199 src/window.py:281 -msgid "Trash" -msgstr "" - -#: data/ui/orphans.blp:130 data/ui/search_install.blp:54 -#: src/orphans_window.py:122 src/window.py:839 -msgid "Install" -msgstr "" - -#: data/ui/popular_remotes.blp:18 -msgid "Add Remote" -msgstr "" - -#: data/ui/popular_remotes.blp:19 -msgid "Choose from a list of popular remotes or add a new one" -msgstr "" - -#: data/ui/popular_remotes.blp:45 data/ui/remotes.blp:51 -msgid "Add a Repo File" -msgstr "" - -#: data/ui/popular_remotes.blp:50 data/ui/remotes.blp:56 -msgid "Add a Custom Remote" -msgstr "" - -#: data/ui/properties.blp:7 -msgid "Properties" -msgstr "" - -#: data/ui/properties.blp:73 -msgid "Loading User Data" -msgstr "" - -#: data/ui/properties.blp:78 src/app_row_widget.py:268 -#: src/orphans_window.py:258 -msgid "Open User Data Folder" -msgstr "" - -#: data/ui/properties.blp:90 src/app_row_widget.py:281 -msgid "Trash User Data" -msgstr "" - -#: data/ui/properties.blp:108 -msgid "Show Apps Using This Runtime" -msgstr "" - -#: data/ui/properties.blp:119 -msgid "Runtime" -msgstr "" - -#: data/ui/properties.blp:124 src/app_row_widget.py:156 -msgid "View Properties" -msgstr "" - -#: data/ui/properties.blp:135 src/app_row_widget.py:234 src/window.py:562 -msgid "Copy" -msgstr "" - -#: data/ui/properties.blp:147 -msgid "Show Details in Store" -msgstr "" - -#: data/ui/remotes.blp:5 src/gtk/help-overlay.blp:42 -msgid "Manage Remotes" -msgstr "" - -#: data/ui/remotes.blp:14 -msgid "Refresh List of Remotes" -msgstr "" - -#: data/ui/remotes.blp:23 -msgid "Installed Remotes" -msgstr "" - -#: data/ui/remotes.blp:27 -msgid "Show Disabled" -msgstr "" - -#: data/ui/remotes.blp:41 -msgid "No Remotes Found" -msgstr "" - -#: data/ui/remotes.blp:45 -msgid "Add a Popular Remote" -msgstr "" - -#: data/ui/remotes.blp:49 -msgid "Add Other Remotes" -msgstr "" - -#: data/ui/remotes.blp:79 -msgid "Adding Remote" -msgstr "" - -#: data/ui/remotes.blp:88 data/ui/window.blp:54 data/ui/window.blp:194 -msgid "This should only take a moment" -msgstr "" - -#: data/ui/search_install.blp:14 -msgid "Search Criteria" -msgstr "" - -#: data/ui/search_install.blp:22 -msgid "Choose a Remote to Search" -msgstr "" - -#: data/ui/search_install.blp:36 -msgid "Results" -msgstr "" - -#: data/ui/search_install.blp:71 -msgid "Start Search" -msgstr "" - -#: data/ui/search_install.blp:80 -msgid "Search for Flatpaks" -msgstr "" - -#: data/ui/search_install.blp:82 -msgid "Search for Flatpaks that you want to install" -msgstr "" - -#: data/ui/search_install.blp:108 -msgid "Searching" -msgstr "" - -#: data/ui/search_install.blp:119 -msgid "Too Many Results" -msgstr "" - -#: data/ui/search_install.blp:120 -msgid "Try being more specific with your search" -msgstr "" - -#: data/ui/snapshots.blp:14 -msgid "Open Snapshots Folder" -msgstr "" - -#: data/ui/snapshots.blp:32 -msgid "New Snapshot" -msgstr "" - -#: data/ui/snapshots.blp:83 -msgid "No Snapshots" -msgstr "" - -#: data/ui/snapshots.blp:84 -msgid "" -"Snapshots are backups of the app's user data. They can be reapplied at any " -"time." -msgstr "" - -#: data/ui/window.blp:18 -msgid "Filter List" -msgstr "" - -#: data/ui/window.blp:24 +#: src/main_window/window.blp:15 msgid "Main Menu" msgstr "" -#: data/ui/window.blp:31 src/gtk/help-overlay.blp:29 -msgid "Toggle Selection Mode" -msgstr "" - -#: data/ui/window.blp:53 -msgid "Loading Flatpaks" -msgstr "" - -#: data/ui/window.blp:129 -msgid "Uninstalling" -msgstr "" - -#: data/ui/window.blp:157 -msgid "Creating Snapshots" -msgstr "" - -#: data/ui/window.blp:169 -msgid "No Flatpaks Found" -msgstr "" - -#: data/ui/window.blp:170 -msgid "" -"Warehouse cannot see the list of installed Flatpaks or the system has no " -"Flatpaks installed" -msgstr "" - -#: data/ui/window.blp:175 -msgid "No Flatpaks Match Filters" -msgstr "" - -#: data/ui/window.blp:176 -msgid "No installed Flatpak matches all of the currently applied filters" -msgstr "" - -#: data/ui/window.blp:193 -msgid "Refreshing List" -msgstr "" - -#: data/ui/window.blp:212 -msgid "Uninstall Selected Apps" -msgstr "" - -#: data/ui/window.blp:218 -msgid "Send Selected Apps' Data to the Trash" -msgstr "" - -#: data/ui/window.blp:224 -msgid "Open Copy Menu" -msgstr "" - -#: data/ui/window.blp:231 -msgid "Snapshot Selected Apps' Data" -msgstr "" - -#: data/ui/window.blp:241 +#: src/main_window/window.blp:34 msgid "Manage Leftover Data…" msgstr "" -#: data/ui/window.blp:250 +#: src/main_window/window.blp:43 msgid "Manage Remotes…" msgstr "" -#: data/ui/window.blp:256 +#: src/main_window/window.blp:49 msgid "Install From File…" msgstr "" -#: data/ui/window.blp:261 +#: src/main_window/window.blp:54 msgid "Install From The Web…" msgstr "" -#: data/ui/window.blp:267 +#: src/main_window/window.blp:60 msgid "Refresh List" msgstr "" -#: data/ui/window.blp:271 +#: src/main_window/window.blp:64 msgid "_Keyboard Shortcuts" msgstr "" -#: data/ui/window.blp:276 +#: src/main_window/window.blp:69 msgid "_About Warehouse" msgstr "" -#: data/ui/window.blp:285 -msgid "Copy Names" -msgstr "" - -#: data/ui/window.blp:290 -msgid "Copy IDs" -msgstr "" - -#: data/ui/window.blp:295 -msgid "Copy Refs" -msgstr "" - -#: src/app_row_widget.py:82 -msgid "Updates Disabled" -msgstr "" - -#: src/app_row_widget.py:87 src/properties_window.py:212 -msgid "{} is masked and will not be updated" -msgstr "" - -#: src/app_row_widget.py:94 -msgid "Auto Removal Disabled" -msgstr "" - -#: src/app_row_widget.py:99 -msgid "" -"{} is pinned and will not be auto removed even when it's required by no app" -msgstr "" - -#: src/app_row_widget.py:106 -msgid "App EOL" -msgstr "" - -#: src/app_row_widget.py:112 src/properties_window.py:195 -msgid "" -"{} has reached its End of Life and will not receive any security updates" -msgstr "" - -#: src/app_row_widget.py:123 -msgid "Runtime EOL" -msgstr "" - -#: src/app_row_widget.py:129 src/properties_window.py:203 -msgid "" -"{}'s runtime has reached its End of Life and will not receive any security " -"updates" -msgstr "" - -#: src/app_row_widget.py:165 src/orphans_window.py:266 -msgid "Select" -msgstr "" - -#: src/app_row_widget.py:176 src/remotes_window.py:227 -msgid "View More" -msgstr "" - -#: src/app_row_widget.py:197 -msgid "Copied name" -msgstr "" - -#: src/app_row_widget.py:201 -msgid "Copy Name" -msgstr "" - -#: src/app_row_widget.py:206 -msgid "Copied ID" -msgstr "" - -#: src/app_row_widget.py:211 -msgid "Copy ID" -msgstr "" - -#: src/app_row_widget.py:216 -msgid "Copied ref" -msgstr "" - -#: src/app_row_widget.py:221 -msgid "Copy Ref" -msgstr "" - -#: src/app_row_widget.py:227 -msgid "Copied launch command" -msgstr "" - -#: src/app_row_widget.py:231 -msgid "Copy Launch Command" -msgstr "" - -#: src/app_row_widget.py:243 -msgid "Opened {}" -msgstr "" - -#: src/app_row_widget.py:246 -msgid "Open" -msgstr "" - -#: src/app_row_widget.py:256 src/window.py:229 src/window.py:302 -msgid "Uninstall" -msgstr "" - -#: src/app_row_widget.py:311 -msgid "Enable Updates" -msgstr "" - -#: src/app_row_widget.py:324 src/window.py:546 -msgid "Disable Auto Removal" -msgstr "" - -#: src/app_row_widget.py:336 -msgid "Enable Auto Removal" -msgstr "" - -#: src/app_row_widget.py:350 -msgid "Manage Snapshots" -msgstr "" - #: src/common.py:325 src/common.py:399 msgid "" "Working on {}\n" "{} out of {}" msgstr "" -#: src/downgrade_window.py:88 -msgid "Commit Hash: {}" -msgstr "" - -#: src/downgrade_window.py:110 -msgid "Could not downgrade {}" -msgstr "" - -#: src/downgrade_window.py:128 -msgid "Downgrading…" -msgstr "" - -#: src/downgrade_window.py:151 -msgid "Downgrade {}" -msgstr "" - -#: src/downgrade_window.py:159 -msgid "Ensure that {} will never be updated to a newer version" -msgstr "" - -#: src/filter_window.py:43 src/filter_window.py:49 -msgid "{} selected" -msgstr "" - -#: src/filter_window.py:135 src/orphans_window.py:150 src/remotes_window.py:307 -msgid "User wide" -msgstr "" - -#: src/filter_window.py:137 src/orphans_window.py:152 src/remotes_window.py:309 -msgid "System wide" -msgstr "" - -#: src/filter_window.py:139 src/orphans_window.py:154 src/remotes_window.py:311 -msgid "Unknown install type" -msgstr "" - #: src/gtk/help-overlay.blp:11 msgid "App Management" msgstr "" @@ -555,18 +111,30 @@ msgstr "" msgid "Search" msgstr "" +#: src/gtk/help-overlay.blp:19 +msgid "Set Filters" +msgstr "" + #: src/gtk/help-overlay.blp:24 msgid "Refresh" msgstr "" +#: src/gtk/help-overlay.blp:29 +msgid "Toggle Selection Mode" +msgstr "" + #: src/gtk/help-overlay.blp:34 msgid "More Functions" msgstr "" -#: src/gtk/help-overlay.blp:37 src/orphans_window.py:30 +#: src/gtk/help-overlay.blp:37 msgid "Manage Leftover Data" msgstr "" +#: src/gtk/help-overlay.blp:42 +msgid "Manage Remotes" +msgstr "" + #: src/gtk/help-overlay.blp:47 msgid "Install From File" msgstr "" @@ -587,458 +155,19 @@ msgstr "" msgid "Quit" msgstr "" -#: src/main.py:149 +#: src/main.py:145 msgid "Flatpaks" msgstr "" #. Translators: do one of the following, one per line: Your Name, Your Name , Your Name https://websi.te -#: src/main.py:191 +#: src/main.py:187 msgid "translator-credits" msgstr "" -#: src/main.py:197 +#: src/main.py:193 msgid "Donate" msgstr "" -#: src/main.py:199 +#: src/main.py:195 msgid "Contributors" msgstr "" - -#: src/orphans_window.py:80 src/window.py:810 -msgid "Installed successfully" -msgstr "" - -#: src/orphans_window.py:83 -msgid "Could not install some apps" -msgstr "" - -#: src/orphans_window.py:117 -msgid "Attempt to Install?" -msgstr "" - -#: src/orphans_window.py:118 -msgid "Warehouse will attempt to install apps matching the selected data." -msgstr "" - -#: src/orphans_window.py:121 src/orphans_window.py:189 -#: src/properties_window.py:156 src/remotes_window.py:86 -#: src/remotes_window.py:167 src/remotes_window.py:450 -#: src/remotes_window.py:621 src/snapshots_window.py:128 -#: src/snapshots_window.py:234 src/window.py:228 src/window.py:301 -#: src/window.py:446 src/window.py:492 src/window.py:543 src/window.py:645 -#: src/window.py:715 src/window.py:838 -msgid "Cancel" -msgstr "" - -#: src/orphans_window.py:185 -msgid "Trash folders?" -msgstr "" - -#: src/orphans_window.py:185 -msgid "These folders will be sent to the trash." -msgstr "" - -#: src/orphans_window.py:190 -msgid "Continue" -msgstr "" - -#: src/orphans_window.py:198 src/properties_window.py:46 -#: src/snapshots_window.py:245 src/window.py:424 -msgid "Could not open folder" -msgstr "" - -#: src/orphans_window.py:216 -msgid "Could not manage data" -msgstr "" - -#: src/properties_window.py:40 src/remotes_window.py:212 -msgid "Copied {}" -msgstr "" - -#: src/properties_window.py:52 -msgid "Could not show details" -msgstr "" - -#: src/properties_window.py:58 -msgid "User Data" -msgstr "" - -#: src/properties_window.py:69 -msgid "Description" -msgstr "" - -#: src/properties_window.py:84 src/properties_window.py:144 -msgid "No User Data" -msgstr "" - -#: src/properties_window.py:135 -msgid "Could not show properties" -msgstr "" - -#: src/properties_window.py:143 src/window.py:438 -msgid "Trashed user data" -msgstr "" - -#: src/properties_window.py:150 src/window.py:433 -msgid "Could not trash user data" -msgstr "" - -#: src/properties_window.py:154 src/window.py:441 -msgid "Send {}'s User Data to the Trash?" -msgstr "" - -#: src/properties_window.py:158 src/window.py:448 src/window.py:646 -msgid "Trash Data" -msgstr "" - -#: src/remotes_window.py:71 -msgid "Could not remove {}" -msgstr "" - -#: src/remotes_window.py:81 src/remotes_window.py:162 -msgid "Any installed apps from {} will stop receiving updates" -msgstr "" - -#: src/remotes_window.py:84 -msgid "Remove {}?" -msgstr "" - -#: src/remotes_window.py:87 src/remotes_window.py:272 -msgid "Remove" -msgstr "" - -#: src/remotes_window.py:114 -msgid "Could not enable {}" -msgstr "" - -#: src/remotes_window.py:142 -msgid "Could not disable {}" -msgstr "" - -#: src/remotes_window.py:165 -msgid "Disable {}?" -msgstr "" - -#: src/remotes_window.py:168 src/remotes_window.py:263 -msgid "Disable" -msgstr "" - -#: src/remotes_window.py:179 -msgid "Could not view apps" -msgstr "" - -#: src/remotes_window.py:240 -msgid "Set Filter" -msgstr "" - -#: src/remotes_window.py:254 -msgid "Enable" -msgstr "" - -#: src/remotes_window.py:288 -msgid "Copy remote name" -msgstr "" - -#: src/remotes_window.py:302 -msgid "Disabled" -msgstr "" - -#: src/remotes_window.py:339 -msgid "The open source, pay-what-you-want app store from elementary" -msgstr "" - -#: src/remotes_window.py:345 -msgid "Central repository of Flatpak applications" -msgstr "" - -#: src/remotes_window.py:351 -msgid "Beta builds of Flatpak applications" -msgstr "" - -#: src/remotes_window.py:357 -msgid "Flatpaks packaged by Fedora Linux" -msgstr "" - -#: src/remotes_window.py:363 -msgid "The latest beta GNOME Apps and Runtimes" -msgstr "" - -#: src/remotes_window.py:369 -msgid "Beta KDE Apps and Runtimes" -msgstr "" - -#: src/remotes_window.py:375 -msgid "Central repository of the WebKit Developer and Runtime SDK" -msgstr "" - -#: src/remotes_window.py:411 src/remotes_window.py:573 -msgid "Could not add {}" -msgstr "" - -#: src/remotes_window.py:448 -msgid "Add Flatpak Remote" -msgstr "" - -#: src/remotes_window.py:451 src/remotes_window.py:622 -msgid "Add" -msgstr "" - -#: src/remotes_window.py:505 src/remotes_window.py:630 -msgid "Name" -msgstr "" - -#: src/remotes_window.py:509 -msgid "URL" -msgstr "" - -#: src/remotes_window.py:521 src/remotes_window.py:633 src/window.py:847 -msgid "User" -msgstr "" - -#: src/remotes_window.py:521 src/remotes_window.py:633 -msgid "Remote will be available to only you" -msgstr "" - -#: src/remotes_window.py:529 src/remotes_window.py:636 src/window.py:850 -msgid "System" -msgstr "" - -#: src/remotes_window.py:530 src/remotes_window.py:637 -msgid "Remote will be available to every user on the system" -msgstr "" - -#: src/remotes_window.py:569 -msgid "{} successfully added" -msgstr "" - -#: src/remotes_window.py:619 -msgid "Add {}?" -msgstr "" - -#: src/remotes_window.py:667 -msgid "Flatpak Repos" -msgstr "" - -#: src/snapshots_window.py:48 -msgid "There is no User Data to Snapshot" -msgstr "" - -#: src/snapshots_window.py:87 -msgid "Version {}" -msgstr "" - -#: src/snapshots_window.py:95 src/snapshots_window.py:236 -msgid "Apply Snapshot" -msgstr "" - -#: src/snapshots_window.py:101 src/snapshots_window.py:130 -msgid "Trash Snapshot" -msgstr "" - -#: src/snapshots_window.py:121 -msgid "Could not trash snapshot" -msgstr "" - -#: src/snapshots_window.py:125 -msgid "Trash Snapshot?" -msgstr "" - -#: src/snapshots_window.py:126 -msgid "This snapshot and its contents will be sent to the trash." -msgstr "" - -#: src/snapshots_window.py:148 -msgid "Could not create snapshot" -msgstr "" - -#: src/snapshots_window.py:157 -msgid "Creating Snapshot…" -msgstr "" - -#: src/snapshots_window.py:191 src/snapshots_window.py:208 -#: src/snapshots_window.py:215 -msgid "Could not apply snapshot" -msgstr "" - -#: src/snapshots_window.py:194 -msgid "Snapshot applied" -msgstr "" - -#: src/snapshots_window.py:221 -msgid "Applying Snapshot…" -msgstr "" - -#: src/snapshots_window.py:229 -msgid "Apply Snapshot?" -msgstr "" - -#: src/snapshots_window.py:230 -msgid "Applying this snapshot will trash any current user data for {}." -msgstr "" - -#. Window stuffs -#: src/snapshots_window.py:280 -msgid "{} Snapshots" -msgstr "" - -#: src/window.py:111 -msgid "Uninstalled successfully" -msgstr "" - -#: src/window.py:114 -msgid "Could not uninstall some apps" -msgstr "" - -#: src/window.py:170 -msgid "Uninstall Selected Apps?" -msgstr "" - -#: src/window.py:171 -msgid "It will not be possible to use these apps after removal." -msgstr "" - -#: src/window.py:191 src/window.py:273 -msgid "App Settings & Data" -msgstr "" - -#: src/window.py:195 src/window.py:277 -msgid "Keep" -msgstr "" - -#: src/window.py:196 -msgid "Allow restoring these apps' settings and content" -msgstr "" - -#: src/window.py:200 -msgid "Send these apps' settings and content to the trash" -msgstr "" - -#: src/window.py:236 -msgid "Cannot uninstall while already uninstalling" -msgstr "" - -#: src/window.py:265 -msgid "Uninstall {}?" -msgstr "" - -#: src/window.py:266 -msgid "It will not be possible to use {} after removal." -msgstr "" - -#: src/window.py:278 -msgid "Allow restoring this app's settings and content" -msgstr "" - -#: src/window.py:282 -msgid "Send this app's settings and content to the trash" -msgstr "" - -#: src/window.py:444 -msgid "Your files and data for this app will be sent to the trash." -msgstr "" - -#: src/window.py:463 -msgid "Could not disable updates for {}" -msgstr "" - -#: src/window.py:485 -msgid "Disable Updates for {}?" -msgstr "" - -#: src/window.py:489 -msgid "" -"This will mask {} ensuring it will never recieve any feature or security " -"updates." -msgstr "" - -#: src/window.py:511 -msgid "Could not enable auto removal" -msgstr "" - -#: src/window.py:514 -msgid "Could not disable auto removal" -msgstr "" - -#: src/window.py:537 -msgid "Disable Automatic Removal for {}?" -msgstr "" - -#: src/window.py:540 -msgid "" -"This will pin {} ensuring it well never be removed automatically, even if no " -"app depends on it." -msgstr "" - -#: src/window.py:560 -msgid "Could not Run App" -msgstr "" - -#: src/window.py:568 -msgid "OK" -msgstr "" - -#: src/window.py:620 -msgid "{} has no data to trash" -msgstr "" - -#: src/window.py:626 -msgid "Could not trash {}'s data" -msgstr "" - -#: src/window.py:641 -msgid "Trash Selected Apps' User Data?" -msgstr "" - -#: src/window.py:642 -msgid "Your files and data for these apps will be sent to the trash." -msgstr "" - -#: src/window.py:692 -msgid "Could not snapshot some apps" -msgstr "" - -#: src/window.py:709 -msgid "Create Snapshots?" -msgstr "" - -#: src/window.py:711 -msgid "" -"Snapshots are backups of the app's user data. They can be reapplied at any " -"time. This could take a while." -msgstr "" - -#: src/window.py:716 -msgid "Create Snapshots" -msgstr "" - -#: src/window.py:771 -msgid "Copied selected app names" -msgstr "" - -#: src/window.py:786 -msgid "Copied selected app IDs" -msgstr "" - -#: src/window.py:801 -msgid "Copied selected app refs" -msgstr "" - -#: src/window.py:812 -msgid "Could not install app" -msgstr "" - -#: src/window.py:836 -msgid "Install {}?" -msgstr "" - -#: src/window.py:847 -msgid "The app will be available to only you" -msgstr "" - -#: src/window.py:851 -msgid "The app will be available to every user on the system" -msgstr "" - -#: src/window.py:881 -msgid "File type not supported" -msgstr "" diff --git a/src/app_row_widget.py b/src/app_row_widget.py deleted file mode 100644 index 716322d..0000000 --- a/src/app_row_widget.py +++ /dev/null @@ -1,381 +0,0 @@ -import os -import pathlib -import subprocess -import re - -from gi.repository import Adw, Gdk, Gio, GLib, Gtk -from .properties_window import PropertiesWindow -from .downgrade_window import DowngradeWindow -from .snapshots_window import SnapshotsWindow -from .filter_window import FilterWindow -from .common import myUtils - - -class AppRow(Adw.ActionRow): - def set_selectable(self, is_selectable): - self.tickbox.set_active(False) - self.tickbox.set_visible(is_selectable) - self.row_menu.set_visible(not is_selectable) - self.set_activatable(is_selectable) - - def set_is_visible(self, is_visible): - self.set_visible(is_visible) - self.set_selectable(False) - - def info_button_show_or_hide(self): - self.info_button.set_visible(False) - - if self.mask_label.get_visible() == True: - self.info_button.set_visible(True) - return - - if self.pin_label.get_visible() == True: - self.info_button.set_visible(True) - return - - if self.eol_app_label.get_visible() == True: - self.info_button.set_visible(True) - return - - if self.eol_runtime_label.get_visible() == True: - self.info_button.set_visible(True) - return - - def set_masked(self, is_masked): - self.mask_label.set_visible(is_masked) - self.info_button_show_or_hide() - - def __init__(self, parent_window, host_flatpaks, index, **kwargs): - super().__init__(**kwargs) - self.my_utils = myUtils(parent_window) - - current_flatpak = host_flatpaks[index] - - self.index = index - self.app_name = current_flatpak[0] - self.app_id = current_flatpak[2] - self.app_version = current_flatpak[3] - self.origin_remote = current_flatpak[6] - self.install_type = current_flatpak[7] - self.app_ref = current_flatpak[8] - self.dependent_runtime = current_flatpak[13] - - self.set_title(self.app_name) - self.set_subtitle(self.app_id) - self.add_prefix(self.my_utils.find_app_icon(self.app_id)) - - self.is_runtime = False - if len(current_flatpak[13]) == 0: - self.is_runtime = True - - info_box = Gtk.Box( - orientation=Gtk.Orientation.VERTICAL, - valign=Gtk.Align.CENTER, - halign=Gtk.Align.CENTER, - hexpand=True, - vexpand=True, - spacing=6, - ) - - # justify=Gtk.Justification.RIGHT - self.mask_label = Gtk.Label( - label=_("Updates Disabled"), - visible=False, - hexpand=True, - wrap=True, - valign=Gtk.Align.CENTER, - tooltip_text=_("{} is masked and will not be updated").format( - self.app_name - ), - ) - self.mask_label.add_css_class("warning") - - self.pin_label = Gtk.Label( - label=_("Auto Removal Disabled"), - visible=False, - hexpand=True, - wrap=True, - valign=Gtk.Align.CENTER, - tooltip_text=_("{} is pinned and will not be auto removed even when it's required by no app").format( - self.app_name - ) - ) - self.pin_label.add_css_class("warning") - - self.eol_app_label = Gtk.Label( - label=_("App EOL"), - visible=False, - hexpand=True, - wrap=True, - valign=Gtk.Align.CENTER, - tooltip_text=_( - "{} has reached its End of Life and will not receive any security updates" - ).format(self.app_name), - ) - self.eol_app_label.add_css_class("error") - info_box.append(self.eol_app_label) - if "eol" in parent_window.host_flatpaks[index][12]: - # EOL = End Of Life, meaning the app will not be updated - # justify=Gtk.Justification.RIGHT - self.eol_app_label.set_visible(True) - - self.eol_runtime_label = Gtk.Label( - label=_("Runtime EOL"), - visible=False, - hexpand=True, - wrap=True, - valign=Gtk.Align.CENTER, - tooltip_text=_( - "{}'s runtime has reached its End of Life and will not receive any security updates" - ).format(self.app_name), - ) - self.eol_runtime_label.add_css_class("error") - info_box.append(self.eol_runtime_label) - if current_flatpak[13] in parent_window.eol_list: - # EOL = End Of Life, meaning the runtime will not be updated - # justify=Gtk.Justification.RIGHT - self.eol_runtime_label.set_visible(True) - - info_pop = Gtk.Popover() - info_pop.set_child(info_box) - self.info_button = Gtk.MenuButton( - visible=False, - valign=Gtk.Align.CENTER, - popover=info_pop, - icon_name="software-update-urgent-symbolic", - ) - self.info_button.add_css_class("flat") - - info_box.append(self.mask_label) - info_box.append(self.pin_label) - self.add_suffix(self.info_button) - - properties_button = Gtk.Button( - icon_name="info-symbolic", - valign=Gtk.Align.CENTER, - tooltip_text=_("View Properties"), - ) - properties_button.add_css_class("flat") - properties_button.connect( - "clicked", lambda *_: PropertiesWindow(index, host_flatpaks, parent_window) - ) - self.add_suffix(properties_button) - - self.tickbox = Gtk.CheckButton( - visible=False, tooltip_text=_("Select") - ) # visible=self.in_batch_mode - self.tickbox.add_css_class("selection-mode") - self.tickbox.connect("toggled", parent_window.row_select_handler) - self.add_suffix(self.tickbox) - self.set_activatable_widget(self.tickbox) - self.set_activatable(False) - - self.row_menu = Gtk.MenuButton( - icon_name="view-more-symbolic", - valign=Gtk.Align.CENTER, - tooltip_text=_("View More"), - ) - self.row_menu.add_css_class("flat") - row_menu_model = Gio.Menu() - copy_menu_model = Gio.Menu() - advanced_menu_model = Gio.Menu() - self.add_suffix(self.row_menu) - - self.is_pinned = False - - if "user" in self.install_type: - if f"runtime/{self.app_ref}" in parent_window.user_pins: - self.is_pinned = True - - if "system" in self.install_type: - if f"runtime/{self.app_ref}" in parent_window.system_pins: - self.is_pinned = True - - parent_window.create_action( - ("copy-name" + str(index)), - lambda *_, name=self.app_name, toast=_( - "Copied name" - ): parent_window.copy_item(name, toast), - ) - copy_menu_model.append_item( - Gio.MenuItem.new(_("Copy Name"), f"win.copy-name{index}") - ) - - parent_window.create_action( - ("copy-id" + str(index)), - lambda *_, id=self.app_id, toast=_("Copied ID"): parent_window.copy_item( - id, toast - ), - ) - copy_menu_model.append_item( - Gio.MenuItem.new(_("Copy ID"), f"win.copy-id{index}") - ) - - parent_window.create_action( - ("copy-ref" + str(index)), - lambda *_, ref=self.app_ref, toast=_("Copied ref"): parent_window.copy_item( - ref, toast - ), - ) - copy_menu_model.append_item( - Gio.MenuItem.new(_("Copy Ref"), f"win.copy-ref{index}") - ) - - parent_window.create_action( - ("copy-command" + str(index)), - lambda *_, ref=self.app_ref, toast=_( - "Copied launch command" - ): parent_window.copy_item(f"flatpak run {ref}", toast), - ) - copy_menu_model.append_item( - Gio.MenuItem.new(_("Copy Launch Command"), f"win.copy-command{index}") - ) - - row_menu_model.append_submenu(_("Copy"), copy_menu_model) - - if ( - "runtime" not in parent_window.host_flatpaks[index][12] - and self.app_id != "io.github.flattool.Warehouse" - ): - parent_window.create_action( - ("run" + str(index)), - lambda *_a, ref=self.app_ref, name=self.app_name: parent_window.run_app_thread( - ref, _("Opened {}").format(name) - ), - ) - run_item = Gio.MenuItem.new(_("Open"), f"win.run{index}") - row_menu_model.append_item(run_item) - - if self.app_id != "io.github.flattool.Warehouse": - parent_window.create_action( - ("uninstall" + str(index)), - lambda *_: parent_window.uninstall_button_handler( - self, self.app_name, self.app_ref, self.app_id - ), - ) - uninstall_item = Gio.MenuItem.new(_("Uninstall"), f"win.uninstall{index}") - row_menu_model.append_item(uninstall_item) - - data_menu_model = Gio.Menu() - - parent_window.create_action( - ("open-data" + str(index)), - lambda *_, path=( - parent_window.user_data_path + self.app_id - ): parent_window.open_data_folder(path), - ) - open_data_item = Gio.MenuItem.new( - _("Open User Data Folder"), f"win.open-data{index}" - ) - open_data_item.set_attribute_value( - "hidden-when", GLib.Variant.new_string("action-disabled") - ) - data_menu_model.append_item(open_data_item) - - parent_window.create_action( - ("trash" + str(index)), - lambda *_, name=self.app_name, id=self.app_id, index=index: parent_window.trash_data( - name, id, index - ), - ) - trash_item = Gio.MenuItem.new(_("Trash User Data"), f"win.trash{index}") - trash_item.set_attribute_value( - "hidden-when", GLib.Variant.new_string("action-disabled") - ) - data_menu_model.append_item(trash_item) - - row_menu_model.append_section(None, data_menu_model) - - if not os.path.exists(parent_window.user_data_path + self.app_id): - parent_window.lookup_action(f"open-data{self.index}").set_enabled(False) - parent_window.lookup_action(f"trash{self.index}").set_enabled(False) - - parent_window.create_action( - ("mask" + str(index)), - lambda *_, id=self.app_id, type=self.install_type, index=index: parent_window.mask_flatpak( - self - ), - ) - mask_item = Gio.MenuItem.new(_("Disable Updates"), f"win.mask{index}") - mask_item.set_attribute_value( - "hidden-when", GLib.Variant.new_string("action-disabled") - ) - advanced_menu_model.append_item(mask_item) - - parent_window.create_action( - ("unmask" + str(index)), - lambda *_, id=self.app_id, type=self.install_type, index=index: parent_window.mask_flatpak( - self - ), - ) - unmask_item = Gio.MenuItem.new(_("Enable Updates"), f"win.unmask{index}") - unmask_item.set_attribute_value( - "hidden-when", GLib.Variant.new_string("action-disabled") - ) - advanced_menu_model.append_item(unmask_item) - - if "runtime" in parent_window.host_flatpaks[index][12]: - parent_window.create_action( - ("pin" + str(index)), - lambda *_, d=self.app_id, type=self.install_type, index=index: parent_window.pin_flatpak( - self - ), - ) - pin_item = Gio.MenuItem.new(_("Disable Auto Removal"), f"win.pin{index}") - pin_item.set_attribute_value( - "hidden-when", GLib.Variant.new_string("action-disabled") - ) - advanced_menu_model.append_item(pin_item) - - parent_window.create_action( - ("unpin" + str(index)), - lambda *_, d=self.app_id, type=self.install_type, index=index: parent_window.pin_flatpak( - self - ), - ) - unpin_item = Gio.MenuItem.new(_("Enable Auto Removal"), f"win.unpin{index}") - unpin_item.set_attribute_value( - "hidden-when", GLib.Variant.new_string("action-disabled") - ) - advanced_menu_model.append_item(unpin_item) - - if "runtime" not in parent_window.host_flatpaks[index][12]: - parent_window.create_action( - ("snapshot" + str(index)), - lambda *_, row=current_flatpak: SnapshotsWindow( - parent_window, row - ), - ) - snapshot_item = Gio.MenuItem.new( - _("Manage Snapshots"), f"win.snapshot{index}" - ) - advanced_menu_model.append_item(snapshot_item) - - parent_window.create_action( - ("downgrade" + str(index)), - lambda *_, row=current_flatpak, index=index: DowngradeWindow( - parent_window, row, index - ), - ) - downgrade_item = Gio.MenuItem.new(_("Downgrade"), f"win.downgrade{index}") - advanced_menu_model.append_item(downgrade_item) - - if ( - self.app_id in parent_window.system_mask_list - or self.app_id in parent_window.user_mask_list - ): - self.mask_label.set_visible(True) - parent_window.lookup_action(f"mask{index}").set_enabled(False) - else: - parent_window.lookup_action(f"unmask{index}").set_enabled(False) - - if self.is_runtime and self.is_pinned: - parent_window.lookup_action(f"pin{index}").set_enabled(False) - elif self.is_runtime: - parent_window.lookup_action(f"unpin{index}").set_enabled(False) - self.pin_label.set_visible(self.is_pinned) - - row_menu_model.append_section(None, advanced_menu_model) - self.row_menu.set_menu_model(row_menu_model) - - self.info_button_show_or_hide() \ No newline at end of file diff --git a/src/common.py b/src/common.py deleted file mode 100644 index 9b58f61..0000000 --- a/src/common.py +++ /dev/null @@ -1,527 +0,0 @@ -from gi.repository import GLib, Gtk, Adw, Gio # , Gdk -import os -import subprocess -import pathlib -import time - - -class myUtils: - def __init__(self, window, **kwargs): - self.parent_window = window - self.host_home = str(pathlib.Path.home()) - self.user_data_path = self.host_home + "/.var/app/" - self.install_success = True - self.uninstall_success = True - self.new_env = dict(os.environ) - self.new_env["LC_ALL"] = "C" - self.is_dialog_open = False - - def trash_folder(self, path): - if not os.path.exists(path): - print("error in common.trashFolder: path does not exists. path =", path) - return 1 - try: - subprocess.run( - ["gio", "trash", path], - capture_output=False, - check=True, - env=self.new_env, - ) - return 0 - except subprocess.CalledProcessError as e: - print("error in common.trashFolder: CalledProcessError:", e) - return 2 - - def get_size_with_format(self, path): - return self.get_size_format(self.get_dir_size(path)) - - def get_size_format(self, b): - factor = 1000 - suffix = "B" - for unit in ["", "K", "M", "G", "T", "P", "E", "Z"]: - if b < factor: - return f"{b:.1f}{unit}{suffix}" - b /= factor - return f"{b:.1f}{suffix}" - - def get_dir_size(self, directory): - """Returns the `directory` size in bytes.""" - total = 0 - try: - # print("[+] Getting the size of", directory) - for entry in os.scandir(directory): - if entry.is_symlink(): - continue # Skip symlinks - if entry.is_file(): - # if it's a file, use stat() function - total += entry.stat().st_size - elif entry.is_dir(): - # if it's a directory, recursively call this function - try: - total += self.get_dir_size(entry.path) - except FileNotFoundError: - pass - except NotADirectoryError: - # if `directory` isn't a directory, get the file size then - return os.path.getsize(directory) - except PermissionError: - # if for whatever reason we can't open the folder, return 0 - return 0 - if total == 0: - return 0 - # Adding 4000 seems to make it more accurate to whatever data we can't scan from within the sandbox - return total + 4000 - - def find_app_icon(self, app_id): - icon_theme = Gtk.IconTheme.new() - icon_theme.add_search_path("/var/lib/flatpak/exports/share/icons/") - icon_theme.add_search_path( - self.host_home + "/.local/share/flatpak/exports/share/icons" - ) - - try: - icon_path = ( - icon_theme.lookup_icon( - app_id, None, 512, 1, self.parent_window.get_direction(), 0 - ) - .get_file() - .get_path() - ) - except GLib.GError: - icon_path = None - if icon_path: - image = Gtk.Image.new_from_file(icon_path) - image.set_icon_size(Gtk.IconSize.LARGE) - image.add_css_class("icon-dropshadow") - else: - image = Gtk.Image.new_from_icon_name("application-x-executable-symbolic") - image.set_icon_size(Gtk.IconSize.LARGE) - return image - - def get_host_updates(self): - list = [] - output = subprocess.run( - ["flatpak-spawn", "--host", "flatpak", "update"], - capture_output=True, - text=True, - env=self.new_env, - ).stdout - lines = output.strip().split("\n") - columns = lines[0].split("\t") - data = [columns] - for line in lines[1:]: - row = line.split("\t") - data.append(row) - - for i in range(len(data)): - if data[i][0].find(".") == 2: - list.append(data[i][2]) - - return list - - def get_host_system_pins(self): - output = subprocess.run( - ["flatpak-spawn", "--host", "flatpak", "pin"], - capture_output=True, - text=True, - env=self.new_env, - ).stdout - data = output.strip().split("\n") - for i in range(len(data)): - data[i] = data[i].strip() - return data - - def get_host_user_pins(self): - output = subprocess.run( - ["flatpak-spawn", "--host", "flatpak", "pin", "--user"], - capture_output=True, - text=True, - env=self.new_env, - ).stdout - data = output.strip().split("\n") - for i in range(len(data)): - data[i] = data[i].strip() - return data - - def get_host_remotes(self): - output = subprocess.run( - [ - "flatpak-spawn", - "--host", - "flatpak", - "remotes", - "--columns=all", - "--show-disabled", - ], - capture_output=True, - text=True, - env=self.new_env, - ).stdout - lines = output.strip().split("\n") - columns = lines[0].split("\t") - data = [columns] - for line in lines[1:]: - row = line.split("\t") - data.append(row) - return data - - def get_host_flatpaks(self): - output = subprocess.run( - ["flatpak-spawn", "--host", "flatpak", "list", "--columns=all"], - capture_output=True, - text=True, - env=self.new_env, - ).stdout - lines = output.strip().split("\n") - columns = lines[0].split("\t") - data = [columns] - for line in lines[1:]: - row = line.split("\t") - data.append(row) - - output = subprocess.run( - ["flatpak-spawn", "--host", "flatpak", "list", "--columns=runtime"], - capture_output=True, - text=True, - env=self.new_env, - ).stdout - lines = output.split("\n") - for i in range(len(data)): - data[i].append(lines[i]) - sorted_array = sorted(data, key=lambda item: item[0].lower()) - return sorted_array - - def get_flatpak_info(self, ref, install_type): - output = subprocess.run( - [ - "flatpak-spawn", "--host", "sh", "-c", - f"flatpak info {ref} --{install_type}" - ], - capture_output=True, - text=True - ).stdout - lines = output.strip().split("\n") - columns = lines[0].split("\t") - data = [columns] - for line in lines[1:]: - row = line.split(": ", 1) - for i in range(len(row)): - row[i] = row[i].strip() - data.append(row) - info = {} - info["name"] = data[0][0] - for i in range(2, len(data)): - if data[i][0] == '': - continue - info[data[i][0]] = data[i][1] - return info - - def get_dependent_runtimes(self): - paks = self.get_host_flatpaks() - dependent_runtimes = [] - for i in range(len(paks)): - current = paks[i] - try: - if current[13] not in dependent_runtimes and current[13] != "": - dependent_runtimes.append(current[13]) - except: - print("Could not get dependent runtime") - return sorted(dependent_runtimes) - - def get_host_masks(self, user_or_system): - output = subprocess.run( - ["flatpak-spawn", "--host", "flatpak", "mask", f"--{user_or_system}"], - capture_output=True, - text=True, - env=self.new_env, - ).stdout - lines = output.strip().split("\n") - for i in range(len(lines)): - lines[i] = lines[i].strip() - return lines - - def mask_flatpak(self, app_id, user_or_system, remove=False): - command = [ - "flatpak-spawn", - "--host", - "flatpak", - "mask", - f"--{user_or_system}", - app_id, - ] - if remove: - command.append("--remove") - response = "" - try: - response = subprocess.run( - command, capture_output=True, text=True, env=self.new_env - ) - except subprocess.CalledProcessError as e: - print(f"Error setting mask for {app_id}:\n", e) - return 1 - if len(response.stderr) > 0: - return 1 - return 0 - - def downgrade_flatpak(self, id, ref, commit, install_type="system", mask=False, mask_list=None): - unmask_cmd = f"flatpak mask --remove --{install_type} {id} && " - update_cmd = f"flatpak updated {ref} --commit={commit} --{install_type} -y" - to_run_cmd = "" - if id in mask_list: - to_run_cmd += unmask_cmd - to_run_cmd += update_cmd - if mask: - to_run_cmd += f" && flatpak mask --{install_type} {id}" - command = [ - "flatpak-spawn", - "--host", "pkexec", - "sh", "-c", - to_run_cmd, - ] - if install_type == "user": - command.remove("pkexec") - try: - subprocess.run( - command, capture_output=True, text=True, env=self.new_env, check=True - ) - except subprocess.CalledProcessError as e: - print("Error in common.downgrade_flatpak:", e.stderr) - return 1 - return 0 - - def uninstall_flatpak( - self, ref_arr, type_arr, should_trash, progress_bar=None, status_label=None - ): - self.uninstall_success = True - print(ref_arr) - to_uninstall = [] - for i in range(len(ref_arr)): - to_uninstall.append([ref_arr[i], type_arr[i]]) - - apps = [] - fails = [] - for i in range(len(to_uninstall)): - ref = to_uninstall[i][0] - id = to_uninstall[i][0].split("/")[0] - app_type = to_uninstall[i][1] - apps.append([ref, id, app_type]) - # apps array guide: [app_ref, app_id, user_or_system_install] - - for i in range(len(apps)): - command = [ - "flatpak-spawn", - "--host", - "flatpak", - "remove", - "-y", - f"--{apps[i][2]}", - apps[i][0], - ] - try: - print(apps) - if status_label: - GLib.idle_add( - status_label.set_label, - _("Working on {}\n{} out of {}").format( - apps[i][0], i + 1, len(apps) - ), - ) - subprocess.run( - command, capture_output=False, check=True, env=self.new_env - ) - if progress_bar: - GLib.idle_add(progress_bar.set_visible, True) - GLib.idle_add(progress_bar.set_fraction, (i + 1.0) / len(ref_arr)) - except subprocess.CalledProcessError: - fails.append(apps[i]) - - if len(fails) > 0: # Run this only if there is 1 or more non uninstalled apps - pk_command = [ - "flatpak-spawn", - "--host", - "pkexec", - "flatpak", - "remove", - "-y", - "--system", - ] - print("second uninstall process") - for i in range(len(fails)): - if fails[i][2] == "user": - self.uninstall_success = False - continue # Skip if app is a user install app - - pk_command.append(fails[i][0]) - try: - print(pk_command) - if progress_bar: - GLib.idle_add(progress_bar.set_visible, True) - GLib.idle_add(progress_bar.set_fraction, 0.9) - subprocess.run( - pk_command, capture_output=False, check=True, env=self.new_env - ) - except subprocess.CalledProcessError: - self.uninstall_success = False - - if should_trash: - host_paks = self.get_host_flatpaks() - host_refs = [] - for i in range(len(host_paks)): - host_refs.append(host_paks[i][8]) - - for i in range(len(apps)): - if apps[i][0] in host_refs: - print(f"{apps[i][1]} is still installed") - else: - self.trash_folder(f"{self.user_data_path}{apps[i][1]}") - - if progress_bar: - GLib.idle_add(progress_bar.set_visible, False) - GLib.idle_add(progress_bar.set_fraction, 0.0) - - def install_flatpak( - self, app_arr, remote, user_or_system, progress_bar=None, status_label=None - ): - self.install_success = True - fails = [] - - for i in range(len(app_arr)): - command = ["flatpak-spawn", "--host", "flatpak", "install"] - if remote != None: - command.append(remote) - command.append(f"--{user_or_system}") - command.append("-y") - command.append(app_arr[i]) - try: - if status_label: - GLib.idle_add( - status_label.set_label, - _("Working on {}\n{} out of {}").format( - app_arr[i], i + 1, len(app_arr) - ), - ) - subprocess.run( - command, capture_output=False, check=True, env=self.new_env - ) - if progress_bar: - GLib.idle_add(progress_bar.set_visible, True) - GLib.idle_add(progress_bar.set_fraction, (i + 1.0) / len(app_arr)) - except subprocess.CalledProcessError: - fails.append(app_arr[i]) - - if (len(fails) > 0) and (user_or_system == "system"): - pk_command = [ - "flatpak-spawn", - "--host", - "pkexec", - "flatpak", - "install", - remote, - f"--{user_or_system}", - "-y", - ] - for i in range(len(fails)): - pk_command.append(fails[i]) - try: - if progress_bar: - GLib.idle_add(progress_bar.set_visible, True) - GLib.idle_add(progress_bar.set_fraction, 0.9) - subprocess.run( - pk_command, capture_output=False, check=True, env=self.new_env - ) - except subprocess.CalledProcessError: - self.install_success = False - - if (len(fails) > 0) and (user_or_system == "user"): - self.install_success = False - - if progress_bar: - GLib.idle_add(progress_bar.set_visible, False) - GLib.idle_add(progress_bar.set_fraction, 0.0) - - def run_app(self, ref): - self.run_app_error = False - self.run_app_error_message = "" - try: - subprocess.run( - ["flatpak-spawn", "--host", "flatpak", "run", ref], - check=True, - env=self.new_env, - start_new_session=True, - capture_output=True, - ) - except subprocess.CalledProcessError as e: - self.run_app_error_message = e.stderr.decode() - self.run_app_error = True - - def get_install_type(self, type_arr): - if "disabled" in type_arr: - return "disabled" - if "user" in type_arr: - return "user" - if "system" in type_arr: - return "system" - - def snapshot_apps( - self, - epoch, - app_snapshot_path_arr, - app_version_arr, - app_user_data_arr, - progress_bar=None, - ): - if not ( - len(app_snapshot_path_arr) == len(app_version_arr) == len(app_user_data_arr) - ): - print( - "error in common.snapshotApp: the lengths of app_snapshot_path_arr, app_version_arr, and app_user_data_arr do not match." - ) - return 1 - - fails = [] - - for i in range(len(app_snapshot_path_arr)): - snapshot_path = app_snapshot_path_arr[i] - version = app_version_arr[i] - user_data = app_user_data_arr[i] - command = [ - "tar", - "cafv", - f"{snapshot_path}{epoch}_{version}.tar.zst", - "-C", - f"{user_data}", - ".", - ] - - try: - if not os.path.exists(snapshot_path): - file = Gio.File.new_for_path(snapshot_path) - file.make_directory() - subprocess.run(command, check=True, env=self.new_env) - if progress_bar: - GLib.idle_add(progress_bar.set_visible, True) - GLib.idle_add( - progress_bar.set_fraction, - (i + 1.0) / len(app_snapshot_path_arr), - ) - except subprocess.CalledProcessError as e: - print("error in common.snapshotApp:", e) - fails.append(user_data) - - if ( - int(time.time()) == epoch - ): # Wait 1s if the snapshot is made too quickly, to prevent overriding a snapshot file - subprocess.run(["sleep", "1s"]) - - if progress_bar: - GLib.idle_add(progress_bar.set_visible, False) - GLib.idle_add(progress_bar.set_fraction, 0.0) - - if len(fails) > 0: - print("These paths could not be archived:") - for i in range(fails): - print(fails[i]) - print("") - return 1 - else: - return 0 diff --git a/src/downgrade_window.py b/src/downgrade_window.py deleted file mode 100644 index b085cd0..0000000 --- a/src/downgrade_window.py +++ /dev/null @@ -1,168 +0,0 @@ -from gi.repository import Gtk, Adw, GLib, Gdk, Gio -from .common import myUtils -import subprocess -import os -import pathlib - - -@Gtk.Template(resource_path="/io/github/flattool/Warehouse/../data/ui/downgrade.ui") -class DowngradeWindow(Adw.Dialog): - __gtype_name__ = "DowngradeWindow" - - new_env = dict(os.environ) - new_env["LC_ALL"] = "C" - - apply_button = Gtk.Template.Child() - versions_group = Gtk.Template.Child() - toast_overlay = Gtk.Template.Child() - mask_row = Gtk.Template.Child() - main_toolbar_view = Gtk.Template.Child() - loading = Gtk.Template.Child() - loading_label = Gtk.Template.Child() - main_stack = Gtk.Template.Child() - outerbox = Gtk.Template.Child() - action_bar = Gtk.Template.Child() - - def selection_handler(self, button, index): - self.action_bar.set_revealed(True) - if button.get_active(): - self.commit_to_use = self.versions[index][0] - - def get_commits(self): - output = subprocess.run( - [ - "flatpak-spawn", "--host", "sh", "-c", - f"LC_ALL=C flatpak remote-info --log {self.remote} {self.app_ref} --{self.install_type}" - ], - capture_output=True, - text=True - ).stdout - lines = output.strip().split("\n") - columns = lines[0].split("\t") - data = [columns] - for line in lines[1:]: - row = line.split("\t") - data.append(row[0].strip()) - - commits = [] - changes = [] - dates = [] - for i in range(len(data)): - line = data[i] - - if "Commit:" in line: - commits.append(line.replace("Commit: ", "")) - - if "Subject:" in line: - changes.append(line.replace("Subject: ", "")) - - if "Date:" in line: - dates.append(line.replace("Date: ", "")) - - for i in range(len(commits)): - self.versions.append([commits[i], changes[i], dates[i]]) - - def commits_callback(self): - group_button = Gtk.CheckButton(visible=False) - self.versions_group.add(group_button) - for i in range(len(self.versions)): - version = self.versions[i] - date_time = version[2].split(" ") - date = date_time[0].split("-") - offset = date_time[2][:3] + ":" + date_time[2][3:] - time = date_time[1].split(":") - display_time = GLib.DateTime.new( - GLib.TimeZone.new(offset), - int(date[0]), - int(date[1]), - int(date[2]), - int(time[0]), - int(time[1]), - int(time[2]), - ) - display_time = display_time.format("%x %X") - change = version[1].split("(") - row = Adw.ActionRow( - title=GLib.markup_escape_text(change[0]), subtitle=str(display_time) - ) - row.set_tooltip_text(_("Commit Hash: {}").format(version[0])) - select = Gtk.CheckButton() - select.connect("toggled", self.selection_handler, i) - select.set_group(group_button) - - version.append(select) - row.set_activatable_widget(select) - row.add_prefix(select) - self.versions_group.add(row) - self.main_stack.set_visible_child(self.outerbox) - self.apply_button.set_visible(True) - self.mask_row.grab_focus() # Don't know why, but I need this in order for escape to close the window - - def generate_list(self): - task = Gio.Task.new(None, None, lambda *_: self.commits_callback()) - task.run_in_thread(lambda *_: self.get_commits()) - - def downgrade_callack(self): - self.set_can_close(True) - - if self.response != 0: - self.parent_window.toast_overlay.add_toast( - Adw.Toast.new(_("Could not downgrade {}").format(self.app_name)) - ) - self.apply_button.set_sensitive(True) - - self.parent_window.refresh_list_of_flatpaks(self) - self.close() - - def downgrade_thread(self): - mask_list = None - if self.install_type == "system": - mask_list = self.parent_window.system_mask_list - if self.install_type == "user": - mask_list = self.parent_window.user_mask_list - self.response = self.my_utils.downgrade_flatpak( - self.app_id, self.app_ref, self.commit_to_use, self.install_type, self.mask_row.get_active(), mask_list - ) - - def on_apply(self): - self.loading_label.set_label(_("Downgrading…")) - self.set_can_close(False) - self.main_stack.set_visible_child(self.loading) - self.apply_button.set_visible(False) - - task = Gio.Task.new(None, None, lambda *_: self.downgrade_callack()) - task.run_in_thread(lambda *_: self.downgrade_thread()) - - def __init__(self, parent_window, flatpak_row, index, **kwargs): - super().__init__(**kwargs) - - # Create Variables - self.my_utils = myUtils(self) - self.app_name = flatpak_row[0] - self.app_id = flatpak_row[2] - self.remote = flatpak_row[6] - self.install_type = flatpak_row[7] - self.app_ref = flatpak_row[8] - self.versions = [] - self.commit_to_use = "" - self.parent_window = parent_window - self.flatpak_row = flatpak_row - self.response = 0 - self.window_title = _("Downgrade {}").format(self.app_name) - self.index = index - - # Connections - self.apply_button.connect("clicked", lambda *_: self.on_apply()) - - # Apply - self.mask_row.set_subtitle( - _("Ensure that {} will never be updated to a newer version").format( - self.app_name - ) - ) - - self.set_title(self.window_title) - - self.generate_list() - - self.present(parent_window) diff --git a/src/filter_window.py b/src/filter_window.py deleted file mode 100644 index 1af34eb..0000000 --- a/src/filter_window.py +++ /dev/null @@ -1,226 +0,0 @@ -from gi.repository import Gtk, Adw, GLib, Gdk, Gio -from .common import myUtils -import subprocess -import os -import pathlib - - -@Gtk.Template(resource_path="/io/github/flattool/Warehouse/../data/ui/filter.ui") -class FilterWindow(Adw.Dialog): - __gtype_name__ = "FilterWindow" - - show_apps_switch = Gtk.Template.Child() - show_runtimes_switch = Gtk.Template.Child() - remotes_expander = Gtk.Template.Child() - runtimes_expander = Gtk.Template.Child() - reset_button = Gtk.Template.Child() - - is_open = False - - def gsettings_bool_set(self, key, value): - self.settings.set_boolean(key, value) - self.check_is_resetable() - self.main_window.apply_filter() - - def check_is_resetable(self): - if not self.show_apps_switch.get_active(): - self.reset_button.set_sensitive(True) - return - if self.show_runtimes_switch.get_active(): - self.reset_button.set_sensitive(True) - return - if self.total_remotes_selected != 0: - self.reset_button.set_sensitive(True) - return - if self.total_runtimes_selected != 0: - self.reset_button.set_sensitive(True) - return - self.reset_button.set_sensitive(False) - - def row_subtitle_updater(self): - if self.total_runtimes_selected > 0: - self.runtimes_expander.set_subtitle( - _("{} selected").format(self.total_runtimes_selected) - ) - else: - self.runtimes_expander.set_subtitle("") - if self.total_remotes_selected > 0: - self.remotes_expander.set_subtitle( - _("{} selected").format(self.total_remotes_selected) - ) - else: - self.remotes_expander.set_subtitle("") - - def reset_filter_gsettings(self): - self.show_apps_switch.set_active(True) - self.show_runtimes_switch.set_active(False) - for button in self.remote_checkboxes: - button.set_active(False) - for button in self.runtime_checkboxes: - button.set_active(False) - for key in self.settings.list_keys(): - self.settings.reset(key) - self.total_remotes_selected = 0 - self.total_runtimes_selected = 0 - self.row_subtitle_updater() - self.reset_button.set_sensitive(False) - - def runtime_handler(self, button, runtime): - if button.get_active(): - self.total_runtimes_selected += 1 - self.runtimes_string = self.runtimes_string.replace("all", "") - self.runtimes_string += f"{runtime}," - else: - self.total_runtimes_selected -= 1 - self.runtimes_string = self.runtimes_string.replace(f"{runtime},", "") - if len(self.runtimes_string) < 1: - self.runtimes_string += "all" - self.settings.set_string("runtimes-list", self.runtimes_string) - self.check_is_resetable() - self.row_subtitle_updater() - self.main_window.apply_filter() - - def remote_handler(self, button, remote, install_type): - if button.get_active(): - self.total_remotes_selected += 1 - self.remotes_string = self.remotes_string.replace("all", "") - self.remotes_string += f"{remote}<>{install_type};" - else: - self.total_remotes_selected -= 1 - self.remotes_string = self.remotes_string.replace( - f"{remote}<>{install_type};", "" - ) - if len(self.remotes_string) < 1: - self.remotes_string += "all" - self.settings.set_string("remotes-list", self.remotes_string) - self.check_is_resetable() - self.row_subtitle_updater() - self.main_window.apply_filter() - - def generate_remotes(self): - if ( - len(self.host_remotes) < 2 - ): # Don't give the ability to filter by remotes if there is only 1 - self.remotes_expander.set_visible(False) - - total = 0 - for i in range(len(self.host_remotes)): - try: - name = self.host_remotes[i][0] - title = self.host_remotes[i][1] - url = self.host_remotes[i][2] - install_type = self.my_utils.get_install_type(self.host_remotes[i][7]) - remote_row = Adw.ActionRow(title=title) - if "disabled" in install_type: - continue - total += 1 - if title == "-": - remote_row.set_title(name) - self.remotes_expander.add_row(remote_row) - label = Gtk.Label(label=("{} wide").format(install_type)) - label.add_css_class("subtitle") - remote_check = Gtk.CheckButton() - if name in self.remotes_string: - remote_check.set_active(True) - self.total_remotes_selected += 1 - remote_check.connect( - "toggled", - lambda button=remote_check, remote=name, install_type=install_type: self.remote_handler( - button, remote, install_type - ), - ) - self.remote_checkboxes.append(remote_check) - - if "user" in install_type: - remote_row.set_subtitle(_("User wide")) - elif "system" in install_type: - remote_row.set_subtitle(_("System wide")) - else: - remote_row.set_subtitle(_("Unknown install type")) - - remote_row.add_suffix(remote_check) - remote_row.set_activatable_widget(remote_check) - except Exception as e: - print( - "error at filter_window.generate_remotes: Could not make remote row. error", - e, - ) - - self.row_subtitle_updater() - if total < 2: - self.remotes_expander.set_visible(False) - - def generate_runtimes(self): - if ( - len(self.dependent_runtimes) < 2 - ): # Don't give the ability to filter by runtimes if there is only 1 - self.runtimes_expander.set_visible(False) - - for current in self.dependent_runtimes: - runtime_row = Adw.ActionRow(title=current) - runtime_check = Gtk.CheckButton() - if current in self.runtimes_string: - runtime_check.set_active(True) - self.total_runtimes_selected += 1 - runtime_check.connect( - "toggled", - lambda button=runtime_check, runtime=current: self.runtime_handler( - button, runtime - ), - ) - self.runtime_checkboxes.append(runtime_check) - runtime_row.add_suffix(runtime_check) - runtime_row.set_activatable_widget(runtime_check) - self.runtimes_expander.add_row(runtime_row) - self.row_subtitle_updater() - - def __init__(self, main_window, **kwargs): - super().__init__(**kwargs) - - # Create Variables - self.main_window = main_window - self.my_utils = myUtils(self) - self.host_remotes = self.my_utils.get_host_remotes() - self.dependent_runtimes = self.my_utils.get_dependent_runtimes() - self.settings = Gio.Settings.new("io.github.flattool.Warehouse.filter") - self.remotes_string = self.settings.get_string("remotes-list") - self.runtimes_string = self.settings.get_string("runtimes-list") - self.remote_checkboxes = [] - self.runtime_checkboxes = [] - self.total_remotes_selected = 0 - self.total_runtimes_selected = 0 - - self.show_apps_switch.set_active(self.settings.get_boolean("show-apps")) - self.show_runtimes_switch.set_active(self.settings.get_boolean("show-runtimes")) - - # Connections - self.show_apps_switch.connect( - "state-set", - lambda button, state: self.gsettings_bool_set("show-apps", state), - ) - self.show_runtimes_switch.connect( - "state-set", - lambda button, state: self.gsettings_bool_set("show-runtimes", state), - ) - self.reset_button.connect("clicked", lambda *_: self.reset_filter_gsettings()) - - # Calls - if self.host_remotes[0][0] == "": - self.remotes_expander.set_visible(False) - else: - self.generate_remotes() - - if self.dependent_runtimes == []: - self.runtimes_expander.set_visible(False) - else: - self.generate_runtimes() - self.check_is_resetable() - - def set_is_open_false(*args): - self.__class__.is_open = False - self.connect("closed", set_is_open_false) - if self.__class__.is_open: - return - else: - self.present(main_window) - self.__class__.is_open = True diff --git a/src/main.py b/src/main.py index 1b3553e..85ec197 100644 --- a/src/main.py +++ b/src/main.py @@ -27,13 +27,7 @@ gi.require_version("Adw", "1") from gi.repository import Gtk, Gio, Adw, GLib from .window import WarehouseWindow -from .remotes_window import RemotesWindow -from .orphans_window import OrphansWindow -from .filter_window import FilterWindow -from .search_install_window import SearchInstallWindow from .const import Config -from .common import myUtils - class WarehouseApplication(Adw.Application): """The main application singleton class.""" @@ -104,14 +98,6 @@ class WarehouseApplication(Adw.Application): lang=lang, ) - self.my_utils = myUtils(self) - total = 0 - for rem in self.my_utils.get_host_remotes(): - if self.my_utils.get_install_type(rem[7]) != "disabled": - total += 1 - if total < 1: - self.lookup_action(f"open-search-install").set_enabled(False) - def open_search_install(self, widget, _): SearchInstallWindow(self.props.active_window) diff --git a/src/main_window/window.blp b/src/main_window/window.blp new file mode 100644 index 0000000..2fa24bd --- /dev/null +++ b/src/main_window/window.blp @@ -0,0 +1,73 @@ +using Gtk 4.0; +using Adw 1; + +template $WarehouseWindow: Adw.ApplicationWindow { + title: "Warehouse"; + content: + Adw.OverlaySplitView { + sidebar: + Adw.ToolbarView main_toolbar_view { + [top] + Adw.HeaderBar header_bar { + [end] + MenuButton main_menu { + icon-name: "open-menu-symbolic"; + tooltip-text: _("Main Menu"); + menu-model: primary_menu; + } + } + } + ; + content: + Adw.ToolbarView { + [top] + Adw.HeaderBar {} + } + ; + } + ; +} + +menu primary_menu { + section { + item { + label: _("Manage Leftover Data…"); + action: "app.manage-data-folders"; + } + + /*item { + label: _("_Preferences"); + action: "app.preferences"; + }*/ + item { + label: _("Manage Remotes…"); + action: "app.show-remotes-window"; + } + } + section { + item { + label: _("Install From File…"); + action: "app.install-from-file"; + } + + item { + label: _("Install From The Web…"); + action: "app.open-search-install"; + } + } + section { + item { + label: _("Refresh List"); + action: "app.refresh-list"; + } + item { + label: _("_Keyboard Shortcuts"); + action: "win.show-help-overlay"; + } + + item { + label: _("_About Warehouse"); + action: "app.about"; + } + } +} \ No newline at end of file diff --git a/src/main_window/window.py b/src/main_window/window.py new file mode 100644 index 0000000..19a69cd --- /dev/null +++ b/src/main_window/window.py @@ -0,0 +1,64 @@ +# window.py +# +# Copyright 2023 Heliguy +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 3 of the License only. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# SPDX-License-Identifier: GPL-3.0-only + +import os +import pathlib +import subprocess +import re +import time + +from gi.repository import Adw, Gdk, Gio, GLib, Gtk +from .const import Config + +@Gtk.Template(resource_path="/io/github/flattool/Warehouse/main_window/window.ui") +class WarehouseWindow(Adw.ApplicationWindow): + __gtype_name__ = "WarehouseWindow" + + def key_handler(self, controller, keyval, keycode, state): + if keyval == Gdk.KEY_w and state == Gdk.ModifierType.CONTROL_MASK: + self.close() + if keyval == Gdk.KEY_Escape: + self.batch_mode_button.set_active(False) + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.set_size_request(360, 360) + self.settings = Gio.Settings.new("io.github.flattool.Warehouse") + self.settings.bind( + "window-width", self, "default-width", Gio.SettingsBindFlags.DEFAULT + ) + self.settings.bind( + "window-height", self, "default-height", Gio.SettingsBindFlags.DEFAULT + ) + self.settings.bind( + "is-maximized", self, "maximized", Gio.SettingsBindFlags.DEFAULT + ) + self.settings.bind( + "is-fullscreen", self, "fullscreened", Gio.SettingsBindFlags.DEFAULT + ) + + event_controller = Gtk.EventControllerKey() + event_controller.connect("key-pressed", self.key_handler) + self.add_controller(event_controller) + + file_drop = Gtk.DropTarget.new(Gio.File, Gdk.DragAction.COPY) + # file_drop.connect("drop", self.drop_callback) + # self.scrolled_window.add_controller(file_drop) + + if Config.DEVEL: + self.add_css_class("devel") diff --git a/src/meson.build b/src/meson.build index 8039d31..cbb0a39 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,18 +1,11 @@ pkgdatadir = join_paths(get_option('prefix'), get_option('datadir'), meson.project_name()) -moduledir = join_paths(pkgdatadir, 'flattool_gui') +moduledir = join_paths(pkgdatadir, 'Warehouse') gnome = import('gnome') blueprints = custom_target('blueprints', input: files( 'gtk/help-overlay.blp', - '../data/ui/window.blp', - '../data/ui/orphans.blp', - '../data/ui/filter.blp', - '../data/ui/remotes.blp', - '../data/ui/downgrade.blp', - '../data/ui/search_install.blp', - '../data/ui/snapshots.blp', - '../data/ui/properties.blp', + 'main_window/window.blp', ), output: '.', command: [find_program('blueprint-compiler'), 'batch-compile', '@OUTPUT@', '@CURRENT_SOURCE_DIR@', '@INPUT@'], @@ -54,31 +47,8 @@ configure_file( warehouse_sources = [ '__init__.py', 'main.py', - 'common.py', - 'window.py', - 'app_row_widget.py', + 'main_window/window.py', '../data/style.css', - - 'properties_window.py', - '../data/ui/properties.blp', - - 'orphans_window.py', - '../data/ui/orphans.blp', - - 'remotes_window.py', - '../data/ui/remotes.blp', - - 'filter_window.py', - '../data/ui/filter.blp', - - 'downgrade_window.py', - '../data/ui/downgrade.blp', - - 'search_install_window.py', - '../data/ui/search_install.blp', - - 'snapshots_window.py', - '../data/ui/snapshots.blp', ] configure_file( diff --git a/src/orphans_window.py b/src/orphans_window.py deleted file mode 100644 index 89c407b..0000000 --- a/src/orphans_window.py +++ /dev/null @@ -1,353 +0,0 @@ -from gi.repository import Gtk, Adw, GLib, Gdk, Gio -from .common import myUtils -import subprocess -import os -import pathlib - - -@Gtk.Template(resource_path="/io/github/flattool/Warehouse/../data/ui/orphans.ui") -class OrphansWindow(Adw.Dialog): - __gtype_name__ = "OrphansWindow" - - list_of_data = Gtk.Template.Child() - install_button = Gtk.Template.Child() - trash_button = Gtk.Template.Child() - select_all_button = Gtk.Template.Child() - main_overlay = Gtk.Template.Child() - toast_overlay = Gtk.Template.Child() - main_stack = Gtk.Template.Child() - no_data = Gtk.Template.Child() - no_results = Gtk.Template.Child() - action_bar = Gtk.Template.Child() - search_button = Gtk.Template.Child() - search_bar = Gtk.Template.Child() - search_entry = Gtk.Template.Child() - oepn_folder_button = Gtk.Template.Child() - installing = Gtk.Template.Child() - main_box = Gtk.Template.Child() - installing_status = Gtk.Template.Child() - - window_title = _("Manage Leftover Data") - host_home = str(pathlib.Path.home()) - user_data_path = host_home + "/.var/app/" - should_select_all = False - selected_remote = "" - selected_remote_install_type = "" - is_result = False - is_installing = False - is_open = False - - def key_handler(self, controller, keyval, keycode, state): - if keyval == Gdk.KEY_Escape or ( - keyval == Gdk.KEY_w and state == Gdk.ModifierType.CONTROL_MASK - ): - self.close() - - def selection_handler(self, widget, dir_name): - if widget.get_active(): - self.selected_dirs.append(dir_name) - else: - self.selected_dirs.remove(dir_name) - - if len(self.selected_dirs) == 0: - self.set_title( - self.window_title - ) # Set the window title back to what it was when there are no selected dirs - else: - self.set_title( - ("{} selected").format(str(len(self.selected_dirs))) - ) # Set the window title to the amount of selected dirs - - if len(self.selected_dirs) == 0: - self.install_button.set_sensitive(False) - self.trash_button.set_sensitive(False) - else: - self.install_button.set_sensitive(True) - self.trash_button.set_sensitive(True) - - def select_all_handler(self, button): - for check in self.check_buttons: - check.set_active(button.get_active()) - - def install_callback(self, *_args): - self.is_installing = False - self.generate_list() - self.progress_bar.set_visible(False) - self.app_window.refresh_list_of_flatpaks(self) - self.set_can_close(True) # Make window able to close - self.search_button.set_sensitive(True) - if self.my_utils.install_success: - self.toast_overlay.add_toast(Adw.Toast.new(_("Installed successfully"))) - else: - self.toast_overlay.add_toast( - Adw.Toast.new(_("Could not install some apps")) - ) - - def install_handler(self): - self.is_installing = True - self.main_stack.set_visible_child(self.installing) - self.search_button.set_sensitive(False) - self.set_title(self.window_title) - self.keep_checking = True - task = Gio.Task.new(None, None, self.install_callback) - task.run_in_thread( - lambda _task, _obj, _data, _cancellable, id_list=self.selected_dirs, remote=self.selected_remote, app_type=self.selected_remote_type, progress_bar=self.progress_bar, status_label=self.installing_status: self.my_utils.install_flatpak( - id_list, remote, app_type, progress_bar, status_label - ) - ) - - def install_button_handler(self, button): - def remote_select_handler(button, index): - if not button.get_active(): - return - self.selected_remote = self.host_remotes[index][0] - self.selected_remote_type = self.my_utils.get_install_type( - self.host_remotes[index][7] - ) - - def on_response(dialog, response_id, _function): - if response_id == "cancel": - return - self.install_handler() - self.progress_bar.set_visible(True) - self.action_bar.set_visible(False) - self.set_can_close(False) # Make window unable to close - - dialog = Adw.AlertDialog.new( - _("Attempt to Install?"), - _("Warehouse will attempt to install apps matching the selected data."), - ) - dialog.set_close_response("cancel") - dialog.add_response("cancel", _("Cancel")) - dialog.add_response("continue", _("Install")) - dialog.set_response_appearance("continue", Adw.ResponseAppearance.SUGGESTED) - - height = 65 * len(self.host_remotes) - max = 400 - if height > max: - height = max - remotes_scroll = Gtk.ScrolledWindow(vexpand=True, min_content_height=height) - remote_list = Gtk.ListBox(selection_mode="none", valign="start") - remotes_scroll.set_child(remote_list) - remote_list.add_css_class("boxed-list") - - total_added = 0 - remote_select_buttons = [] - for i in range(len(self.host_remotes)): - title = self.host_remotes[i][1] - name = self.host_remotes[i][0] - type_arr = self.host_remotes[i][7] - if "disabled" in type_arr: - continue - remote_row = Adw.ActionRow(title=title) - remote_select = Gtk.CheckButton() - remote_select_buttons.append(remote_select) - remote_select.connect("toggled", remote_select_handler, i) - remote_row.set_activatable_widget(remote_select) - - type = self.my_utils.get_install_type(type_arr) - if type == "user": - remote_row.set_subtitle(_("User wide")) - elif type == "system": - remote_row.set_subtitle(_("System wide")) - else: - remote_row.set_subtitle(_("Unknown install type")) - - if remote_row.get_title() == "-": - remote_row.set_title(self.host_remotes[i][0]) - - if total_added > 0: - remote_select.set_group(remote_select_buttons[0]) - - remote_row.add_prefix(remote_select) - remote_list.append(remote_row) - total_added += 1 - - remote_select_buttons[0].set_active(True) - - if total_added > 1: - dialog.set_extra_child(remotes_scroll) - - dialog.connect("response", on_response, dialog.choose_finish) - dialog.present(self) - - def trash_handler(self, button): - def on_response(dialog, response_id, _function): - if response_id == "cancel": - return - for i in range(len(self.selected_dirs)): - path = self.user_data_path + self.selected_dirs[i] - self.my_utils.trash_folder(path) - self.select_all_button.set_active(False) - self.generate_list() - - dialog = Adw.AlertDialog.new( - _("Trash folders?"), _("These folders will be sent to the trash.") - ) - dialog.connect("response", on_response, dialog.choose_finish) - dialog.set_close_response("cancel") - dialog.add_response("cancel", _("Cancel")) - dialog.add_response("continue", _("Continue")) - dialog.set_response_appearance("continue", Adw.ResponseAppearance.DESTRUCTIVE) - dialog.present(self) - - def open_button_handler(self, _widget, path=user_data_path): - try: - Gio.AppInfo.launch_default_for_uri(f"file://{path}", None) - except GLib.GError: - selt.toast_overlay.add_toast(Adw.Toast.new(_("Could not open folder"))) - - def size_callback(self, row_index): - row = self.list_of_data.get_row_at_index(row_index) - row.set_subtitle(f"~{self.data_rows[row_index][1]}") - - def size_thread(self, index, path): - size = self.my_utils.get_size_with_format(path) - self.data_rows[index].append(size) - - # Create the list of folders in the window - def generate_list(self): - self.data_rows = [] - self.check_buttons = [] - self.host_flatpaks = self.my_utils.get_host_flatpaks() - - if self.host_flatpaks == [["", ""]]: - self.app_window.toast_overlay.add_toast( - Adw.Toast.new(_("Could not manage data")) - ) - self.this_just_crashes_the_window_so_it_doesnt_open() - return - - self.list_of_data.remove_all() - self.selected_dirs = [] - self.set_title(self.window_title) - dir_list = os.listdir(self.user_data_path) - - # This is a list that only holds IDs of install flatpaks - id_list = [] - for i in range(len(self.host_flatpaks)): - try: - id_list.append(self.host_flatpaks[i][2]) - except: - print("Could not get data") - - for i in range(len(dir_list)): - dir_name = dir_list[i] - - # Skip item if it has a matching flatpak - if dir_name in id_list: - continue - - # Create row element - dir_row = Adw.ActionRow(title=dir_name) - self.data_rows.append([dir_row]) - path = self.user_data_path + dir_name - index = len(self.data_rows) - 1 - task = Gio.Task.new( - None, None, lambda *_, index=index: self.size_callback(index) - ) - task.run_in_thread( - lambda _task, _obj, _data, _cancellable, *_, index=index: self.size_thread( - index, path - ) - ) - - open_row_button = Gtk.Button( - icon_name="document-open-symbolic", - valign=Gtk.Align.CENTER, - tooltip_text=_("Open User Data Folder"), - ) - open_row_button.add_css_class("flat") - open_row_button.connect( - "clicked", self.open_button_handler, (self.user_data_path + dir_name) - ) - dir_row.add_suffix(open_row_button) - - select_button = Gtk.CheckButton(tooltip_text=_("Select")) - self.check_buttons.append(select_button) - select_button.add_css_class("selection-mode") - select_button.connect("toggled", self.selection_handler, dir_name) - dir_row.add_suffix(select_button) - dir_row.set_activatable_widget(select_button) - - # Add row to list - self.list_of_data.append(dir_row) - - if self.list_of_data.get_row_at_index(0) == None: - self.main_stack.set_visible_child(self.no_data) - self.action_bar.set_visible(False) - else: - self.main_stack.set_visible_child(self.main_box) - self.action_bar.set_visible(True) - - def filter_func(self, row): - if self.search_entry.get_text().lower() in row.get_title().lower(): - self.is_result = True - return True - - def on_invalidate(self, row): - if self.is_installing: - return - if self.list_of_data.get_row_at_index(0) == None: - self.main_stack.set_visible_child(self.no_data) - self.action_bar.set_visible(False) - else: - self.main_stack.set_visible_child(self.main_box) - self.action_bar.set_visible(True) - - self.is_result = False - self.list_of_data.invalidate_filter() - if self.is_result == False: - self.main_stack.set_visible_child(self.no_results) - self.action_bar.set_visible(False) - - def on_change(self, prop, prop2): - if self.is_installing: - return - if self.search_bar.get_search_mode() == False: - if self.list_of_data.get_row_at_index(0) == None: - self.main_stack.set_visible_child(self.no_data) - self.action_bar.set_visible(False) - else: - self.main_stack.set_visible_child(self.main_box) - self.action_bar.set_visible(True) - - def __init__(self, main_window, **kwargs): - super().__init__(**kwargs) - self.my_utils = myUtils( - self - ) # Access common utils and set the window to this window - self.host_remotes = self.my_utils.get_host_remotes() - self.host_flatpaks = self.my_utils.get_host_flatpaks() - - self.progress_bar = Gtk.ProgressBar(visible=False) - self.progress_bar.add_css_class("osd") - self.app_window = main_window - - self.generate_list() - - event_controller = Gtk.EventControllerKey() - event_controller.connect("key-pressed", self.key_handler) - self.add_controller(event_controller) - - self.install_button.connect("clicked", self.install_button_handler) - if self.host_remotes[0][0] == "": - self.install_button.set_visible(False) - self.trash_button.connect("clicked", self.trash_handler) - self.select_all_button.connect("toggled", self.select_all_handler) - self.main_overlay.add_overlay(self.progress_bar) - - self.list_of_data.set_filter_func(self.filter_func) - self.search_entry.connect("search-changed", self.on_invalidate) - self.search_bar.connect("notify", self.on_change) - self.search_bar.connect_entry(self.search_entry) - self.oepn_folder_button.connect("clicked", self.open_button_handler) - - def set_is_open_false(*args): - self.__class__.is_open = False - self.connect("closed", set_is_open_false) - if self.__class__.is_open: - return - else: - self.present(main_window) - self.__class__.is_open = True diff --git a/src/properties_window.py b/src/properties_window.py deleted file mode 100644 index f71000e..0000000 --- a/src/properties_window.py +++ /dev/null @@ -1,217 +0,0 @@ -from gi.repository import Gtk, Adw, GLib, Gdk, Gio -from .common import myUtils -import subprocess -import os -import pathlib - - -@Gtk.Template(resource_path="/io/github/flattool/Warehouse/../data/ui/properties.ui") -class PropertiesWindow(Adw.Dialog): - __gtype_name__ = "PropertiesWindow" - - new_env = dict(os.environ) - new_env["LC_ALL"] = "C" - host_home = str(pathlib.Path.home()) - user_data_path = host_home + "/.var/app/" - - toast_overlay = Gtk.Template.Child() - upper = Gtk.Template.Child() - app_icon = Gtk.Template.Child() - data_row = Gtk.Template.Child() - open_data = Gtk.Template.Child() - trash_data = Gtk.Template.Child() - spinner = Gtk.Template.Child() - details = Gtk.Template.Child() - view_apps = Gtk.Template.Child() - runtime = Gtk.Template.Child() - runtime_properties = Gtk.Template.Child() - runtime_copy = Gtk.Template.Child() - lower = Gtk.Template.Child() - eol_app_banner = Gtk.Template.Child() - eol_runtime_banner = Gtk.Template.Child() - mask_banner = Gtk.Template.Child() - name = Gtk.Template.Child() - description = Gtk.Template.Child() - description_button = Gtk.Template.Child() - - def copy_item(self, to_copy, to_toast=None): - self.get_clipboard().set(to_copy) - if to_toast: - self.toast_overlay.add_toast(Adw.Toast.new(_("Copied {}").format(to_toast))) - - def open_button_handler(self, widget): - try: - Gio.AppInfo.launch_default_for_uri(f"file://{self.user_data_path}", None) - except GLib.GError: - self.toast_overlay.add_toast(Adw.Toast.new(_("Could not open folder"))) - - def show_details(self, widget): - try: - Gio.AppInfo.launch_default_for_uri(f"appstream://{self.app_id}", None) - except GLib.GError: - self.toast_overlay.add_toast(Adw.Toast.new(_("Could not show details"))) - - def get_size_callback(self, *args): - self.open_data.set_visible(True) - self.open_data.connect("clicked", self.open_button_handler) - self.trash_data.set_visible(True) - self.data_row.set_title(_("User Data")) - self.data_row.set_subtitle(f"~{self.size}") - self.spinner.set_visible(False) - - def get_size_thread(self, *args): - self.size = self.my_utils.get_size_with_format(self.user_data_path) - - def generate_upper(self): - self.description_button.connect( - "clicked", - lambda *_a: self.copy_item( - self.description.get_label(), _("Description") - ) - ) - image = self.my_utils.find_app_icon(self.app_id) - self.runtime.set_subtitle(self.current_flatpak[13]) - if image.get_paintable() == None: - self.app_icon.set_from_icon_name(image.get_icon_name()) - else: - self.app_icon.set_from_paintable(image.get_paintable()) - - if os.path.exists(self.user_data_path): - task = Gio.Task.new(None, None, self.get_size_callback) - task.run_in_thread(self.get_size_thread) - else: - self.data_row.set_title("") - self.data_row.set_subtitle(_("No User Data")) - self.spinner.set_visible(False) - - if "runtime" in self.current_flatpak[12]: - # Pak is a runtime - self.runtime.set_visible(False) - if self.app_ref in self.parent_window.dependent_runtimes: - self.view_apps.set_visible(True) - - def generate_lower(self): - info = self.my_utils.get_flatpak_info(self.app_ref, self.install_type) - name_desc = info["name"].split(" - ") - self.name.set_label((name_desc[0])) - try: - self.description.set_label((name_desc[1])) - except: - pass - for key in info.keys(): - if key == "name": - continue - row = Adw.ActionRow( - title=GLib.markup_escape_text(key), - subtitle=GLib.markup_escape_text(info[key]), - activatable=True, - ) - row.add_suffix(Gtk.Image.new_from_icon_name("edit-copy-symbolic")) - row.add_css_class("property") - row.connect( - "activated", - lambda *_a, row=row: self.copy_item( - row.get_subtitle(), row.get_title() - ), - ) - self.lower.add(row) - - def view_apps_handler(self, widget): - settings = Gio.Settings.new("io.github.flattool.Warehouse.filter") - for key in settings.list_keys(): - settings.reset(key) - settings.set_string("runtimes-list", f"{self.app_ref},") - self.parent_window.apply_filter() - self.close() - - def show_properties_handler(self): - runtime = self.current_flatpak[13] - for i in range(len(self.host_flatpaks)): - # open the properties when the flatpak matches the runtime *and* installation type - if runtime in self.host_flatpaks[i][8] and self.install_type in self.host_flatpaks[i][12]: - PropertiesWindow(i, self.host_flatpaks, self.parent_window) - self.close() - return - self.toast_overlay.add_toast(Adw.Toast.new(_("Could not show properties"))) - - def trash_data_handler(self): - def on_response(_none, response, widget): - if response == "cancel": - return - - if self.my_utils.trash_folder(self.user_data_path) == 0: - self.toast_overlay.add_toast(Adw.Toast.new(_("Trashed user data"))) - self.data_row.set_title(_("No User Data")) - self.data_row.set_subtitle("") - self.open_data.set_visible(False) - self.trash_data.set_visible(False) - else: - self.toast_overlay.add_toast( - Adw.Toast.new(_("Could not trash user data")) - ) - - dialog = Adw.AlertDialog.new( - _("Send {}'s User Data to the Trash?").format(self.app_name) - ) - dialog.add_response("cancel", _("Cancel")) - dialog.set_close_response("cancel") - dialog.add_response("continue", _("Trash Data")) - dialog.set_response_appearance("continue", Adw.ResponseAppearance.DESTRUCTIVE) - dialog.connect("response", on_response, dialog.choose_finish) - dialog.present(self) - - def __init__(self, flatpak_index, host_flatpaks, parent_window, **kwargs): - super().__init__(**kwargs) - self.my_utils = myUtils(self) - self.current_flatpak = host_flatpaks[flatpak_index] - self.parent_window = parent_window - self.host_flatpaks = host_flatpaks - self.flatpak_index = flatpak_index - - self.app_name = self.current_flatpak[0] - self.app_id = self.current_flatpak[2] - self.origin_remote = self.current_flatpak[6] - self.install_type = self.current_flatpak[7] - self.app_ref = self.current_flatpak[8] - self.user_data_path += self.app_id - - self.details.connect("activated", self.show_details) - self.runtime_copy.connect( - "clicked", - lambda *_: self.copy_item( - self.runtime.get_subtitle(), self.runtime.get_title() - ), - ) - self.runtime_properties.connect( - "clicked", lambda *_: self.show_properties_handler() - ) - self.view_apps.connect("activated", self.view_apps_handler) - self.trash_data.connect("clicked", lambda *_: self.trash_data_handler()) - - if "eol" in self.current_flatpak[12]: - self.eol_app_banner.set_revealed(True) - self.eol_app_banner.set_title( - _( - "{} has reached its End of Life and will not receive any security updates" - ).format(self.app_name) - ) - - if self.current_flatpak[13] in parent_window.eol_list: - self.eol_runtime_banner.set_revealed(True) - self.eol_runtime_banner.set_title( - _( - "{}'s runtime has reached its End of Life and will not receive any security updates" - ).format(self.app_name) - ) - - if self.app_id in self.my_utils.get_host_masks( - "system" - ) or self.app_id in self.my_utils.get_host_masks("user"): - self.mask_banner.set_revealed(True) - self.mask_banner.set_title( - _("{} is masked and will not be updated").format(self.app_name) - ) - - self.generate_upper() - self.generate_lower() - self.present(parent_window) diff --git a/src/remotes_window.py b/src/remotes_window.py deleted file mode 100644 index 0aa61c0..0000000 --- a/src/remotes_window.py +++ /dev/null @@ -1,710 +0,0 @@ -from gi.repository import Gtk, Adw, GLib, Gdk, Gio -from .common import myUtils -import subprocess -import os -import re - - -@Gtk.Template(resource_path="/io/github/flattool/Warehouse/../data/ui/remotes.ui") -class RemotesWindow(Adw.Dialog): - __gtype_name__ = "RemotesWindow" - - remotes_list = Gtk.Template.Child() - stack = Gtk.Template.Child() - main_group = Gtk.Template.Child() - no_remotes = Gtk.Template.Child() - toast_overlay = Gtk.Template.Child() - popular_remotes_list = Gtk.Template.Child() - add_from_file = Gtk.Template.Child() - custom_remote = Gtk.Template.Child() - refresh = Gtk.Template.Child() - adding = Gtk.Template.Child() - show_disabled_button = Gtk.Template.Child() - show_disabled_button_button_content = Gtk.Template.Child() - show_disabled = False - - is_open = False - rows_in_list = [] - rows_in_popular_list = [] - - def make_toast(self, text): - self.toast_overlay.add_toast(Adw.Toast.new(text)) - - def get_host_flatpaks(self): - output = subprocess.run( - ["flatpak-spawn", "--host", "flatpak", "list", "--columns=all"], - capture_output=True, - text=True, - env=self.new_env, - ).stdout - lines = output.strip().split("\n") - columns = lines[0].split("\t") - data = [columns] - for line in lines[1:]: - row = line.split("\t") - data.append(row) - return data - - def remove_on_response(self, _dialog, response_id, _function, index): - if response_id == "cancel": - return - - name = self.host_remotes[index][0] - title = self.host_remotes[index][1] - install_type = self.host_remotes[index][7] - if "user" in install_type: - install_type = "user" - if "system" in install_type: - install_type = "system" - command = [ - "flatpak-spawn", - "--host", - "flatpak", - "remote-delete", - "--force", - name, - f"--{install_type}", - ] - try: - subprocess.run(command, capture_output=True, check=True, env=self.new_env) - except subprocess.CalledProcessError as e: - self.make_toast(_("Could not remove {}").format(title)) - print("error in remotes_window.remove_on_response: CalledProcessError:", e) - self.generate_list() - - def remove_handler(self, _widget, index, popoever): - popoever.popdown() - name = self.host_remotes[index][0] - title = self.host_remotes[index][1] - install_type = self.host_remotes[index][7] - - body_text = _("Any installed apps from {} will stop receiving updates").format( - name - ) - dialog = Adw.AlertDialog.new(_("Remove {}?").format(title), body_text) - dialog.set_close_response("cancel") - dialog.add_response("cancel", _("Cancel")) - dialog.add_response("continue", _("Remove")) - dialog.set_response_appearance("continue", Adw.ResponseAppearance.DESTRUCTIVE) - dialog.connect("response", self.remove_on_response, dialog.choose_finish, index) - dialog.present(self) - - def enable_handler(self, button, index): - name = self.host_remotes[index][0] - typeArr = self.host_remotes[index][7] - type = "" - if "system" in typeArr: - type = "system" - else: - type = "user" - - try: - command = [ - "flatpak-spawn", - "--host", - "flatpak", - "remote-modify", - name, - f"--{type}", - "--enable", - ] - subprocess.run(command, capture_output=False, check=True, env=self.new_env) - except subprocess.CalledProcessError as e: - self.toast_overlay.add_toast( - Adw.Toast.new(_("Could not enable {}").format(name)) - ) - print( - f"error in remotes_window.enable_handler: could not enable remote {name}:", - e, - ) - - self.generate_list() - - def disable_handler(self, button, index, popoever): - def disable_response(_a, response, _b): - if response == "cancel": - return - try: - command = [ - "flatpak-spawn", - "--host", - "flatpak", - "remote-modify", - name, - f"--{type}", - "--disable", - ] - subprocess.run( - command, capture_output=False, check=True, env=self.new_env - ) - except subprocess.CalledProcessError as e: - self.toast_overlay.add_toast( - Adw.Toast.new(_("Could not disable {}").format(name)) - ) - print( - f"error in remotes_window.enable_handler: could not disable remote {name}:", - e, - ) - - self.generate_list() - - name = self.host_remotes[index][0] - title = self.host_remotes[index][1] - typeArr = self.host_remotes[index][7] - type = "" - if "system" in typeArr: - type = "system" - else: - type = "user" - - popoever.popdown() - - body_text = _("Any installed apps from {} will stop receiving updates").format( - name - ) - dialog = Adw.AlertDialog.new(_("Disable {}?").format(title), body_text) - dialog.set_close_response("cancel") - dialog.add_response("cancel", _("Cancel")) - dialog.add_response("continue", _("Disable")) - dialog.set_response_appearance("continue", Adw.ResponseAppearance.DESTRUCTIVE) - dialog.connect("response", disable_response, dialog.choose_finish) - dialog.present(self) - - def view_paks(self, type, remote): - if "user" in type: - type = "user" - elif "system" in type: - type = "system" - else: - self.make_toast(_("Could not view apps").format(to_copy)) - print( - "error in remotes_window.view_apps(): remote installation type is not either system or user. type is:", - type, - ) - return - settings = Gio.Settings.new("io.github.flattool.Warehouse.filter") - for key in settings.list_keys(): - settings.reset(key) - settings.set_string("remotes-list", f"{remote}<>{type};") - settings.set_boolean("show-runtimes", True) - self.main_window.apply_filter() - self.close() - - def generate_list(self): - if self.show_disabled_button.get_active(): - self.show_disabled_button_button_content.set_icon_name( - "eye-open-negative-filled-symbolic" - ) - else: - self.show_disabled_button_button_content.set_icon_name( - "eye-not-looking-symbolic" - ) - - self.host_remotes = self.my_utils.get_host_remotes() - self.host_flatpaks = self.get_host_flatpaks() - for i in range(len(self.rows_in_list)): - self.remotes_list.remove(self.rows_in_list[i]) - - self.rows_in_list = [] - - def rowCopyHandler(widget, to_copy): - self.main_window.clipboard.set(to_copy) - self.make_toast(_("Copied {}").format(to_copy)) - - self.no_remotes.set_visible(True) - - has_disabled = False - for i in range(len(self.host_remotes)): - try: - name = self.host_remotes[i][0] - title = self.host_remotes[i][1] - install_type = self.host_remotes[i][7] - remote_row = Adw.ActionRow(title=title) - - more = Gtk.MenuButton( - icon_name="view-more-symbolic", - valign=Gtk.Align.CENTER, - tooltip_text=_("View More"), - ) - more.add_css_class("flat") - options = Gtk.Popover() - options_box = Gtk.Box( - halign=Gtk.Align.CENTER, - valign=Gtk.Align.CENTER, - orientation=Gtk.Orientation.VERTICAL, - ) - - filter_button = Gtk.Button() - filter_button.set_child( - Adw.ButtonContent( - icon_name="funnel-symbolic", label=_("Set Filter") - ) - ) - filter_button.add_css_class("flat") - filter_button.connect( - "clicked", - lambda *_, i=i: self.view_paks( - self.host_remotes[i][7], self.host_remotes[i][0] - ), - ) - - enable_button = Gtk.Button(visible=False) - enable_button.set_child( - Adw.ButtonContent( - icon_name="eye-open-negative-filled-symbolic", label=_("Enable") - ) - ) - enable_button.add_css_class("flat") - enable_button.connect("clicked", self.enable_handler, i) - - disable_button = Gtk.Button() - disable_button.set_child( - Adw.ButtonContent( - icon_name="eye-not-looking-symbolic", label=_("Disable") - ) - ) - disable_button.add_css_class("flat") - disable_button.connect("clicked", self.disable_handler, i, options) - - remove_button = Gtk.Button() - remove_button.set_child( - Adw.ButtonContent( - icon_name="user-trash-symbolic", label=_("Remove") - ) - ) - remove_button.add_css_class("flat") - remove_button.connect("clicked", self.remove_handler, i, options) - - options_box.append(filter_button) - options_box.append(enable_button) - options_box.append(disable_button) - options_box.append(remove_button) - options.set_child(options_box) - more.set_popover(options) - - copy_button = Gtk.Button( - icon_name="edit-copy-symbolic", - valign=Gtk.Align.CENTER, - tooltip_text=_("Copy remote name"), - ) - copy_button.add_css_class("flat") - copy_button.connect("clicked", rowCopyHandler, name) - - remote_row.add_suffix(copy_button) - remote_row.add_suffix(more) - - install_type = self.my_utils.get_install_type(install_type) - if install_type == "disabled": - has_disabled = True - if not self.show_disabled_button.get_active(): - continue - - remote_row.set_subtitle(_("Disabled")) - enable_button.set_visible(True) - disable_button.set_visible(False) - remote_row.add_css_class("warning") - elif install_type == "user": - remote_row.set_subtitle(_("User wide")) - elif install_type == "system": - remote_row.set_subtitle(_("System wide")) - else: - remote_row.set_subtitle(_("Unknown install type")) - - url = self.host_remotes[i][2] - if title == "-": - remote_row.set_title(name) - self.remotes_list.add(remote_row) - # subprocess.run(['wget', f'{self.host_remotes[i][11]}']) Idea to display remote icons... Need internet connection. Not sure if that is worth it - self.rows_in_list.append(remote_row) - self.no_remotes.set_visible(False) - except Exception as e: - print( - "error in remotes_window.generate_list: could not add remote. error:", - e, - ) - self.show_disabled_button.set_visible(has_disabled) - - # Popular remotes - for i in range(len(self.rows_in_popular_list)): - self.popular_remotes_list.remove(self.rows_in_popular_list[i]) - - self.rows_in_popular_list = [] - - remotes = [ - # [Name to show in GUI, Name of remote for system, Link to repo to add, Description of remote] - [ - "AppCenter", - "appcenter", - "https://flatpak.elementary.io/repo.flatpakrepo", - _("The open source, pay-what-you-want app store from elementary"), - ], - [ - "Flathub", - "flathub", - "https://dl.flathub.org/repo/flathub.flatpakrepo", - _("Central repository of Flatpak applications"), - ], - [ - "Flathub beta", - "flathub-beta", - "https://flathub.org/beta-repo/flathub-beta.flatpakrepo", - _("Beta builds of Flatpak applications"), - ], - [ - "Fedora", - "fedora", - "oci+https://registry.fedoraproject.org", - _("Flatpaks packaged by Fedora Linux"), - ], - [ - "GNOME Nightly", - "gnome-nightly", - "https://nightly.gnome.org/gnome-nightly.flatpakrepo", - _("The latest beta GNOME Apps and Runtimes"), - ], - [ - "KDE Testing Applications", - "kdeapps", - "https://distribute.kde.org/kdeapps.flatpakrepo", - _("Beta KDE Apps and Runtimes"), - ], - [ - "WebKit Developer SDK", - "webkit-sdk", - "https://software.igalia.com/flatpak-refs/webkit-sdk.flatpakrepo", - _("Central repository of the WebKit Developer and Runtime SDK"), - ], - ] - - host_remotes = self.my_utils.get_host_remotes() - host_remotes_names = [] - - total_added = 0 - - for i in range(len(self.host_remotes)): - host_remotes_names.append(self.host_remotes[i][0]) - - for i in range(len(remotes)): - if remotes[i][1] in host_remotes_names: - continue - - total_added += 1 - row = Adw.ActionRow( - title=remotes[i][0], subtitle=(remotes[i][2]), activatable=True - ) - row.connect("activated", self.add_handler, remotes[i][1], remotes[i][2]) - row.add_suffix(Gtk.Image.new_from_icon_name("right-large-symbolic")) - self.rows_in_popular_list.append(row) - self.popular_remotes_list.add(row) - - self.popular_remotes_list.set_visible(total_added > 0) - - def addRemoteCallback(self, _a, _b): - self.generate_list() - self.stack.set_visible_child(self.main_group) - - def addRemoteThread(self, command): - try: - subprocess.run(command, capture_output=True, check=True, env=self.new_env) - except subprocess.CalledProcessError as e: - self.toast_overlay.add_toast( - Adw.Toast.new(_("Could not add {}").format(self.name_to_add)) - ) - print( - "error in remotes_window.addRemoteThread: could not add remote. error:", - e, - ) - - def on_add_response(self, _dialog, response_id, _function, row): - if response_id == "cancel": - self.should_pulse = False - return - - self.stack.set_visible_child(self.adding) - - install_type = "--user" - if not self.add_as_user: - install_type = "--system" - - self.name_to_add = self.name_to_add.strip() - self.url_to_add = self.url_to_add.strip() - - command = [ - "flatpak-spawn", - "--host", - "flatpak", - "remote-add", - "--if-not-exists", - self.name_to_add, - self.url_to_add, - install_type, - ] - task = Gio.Task.new(None, None, self.addRemoteCallback) - task.run_in_thread( - lambda _task, _obj, _data, _cancellable: self.addRemoteThread(command) - ) - - def add_handler(self, row, name="", link=""): - dialog = Adw.AlertDialog.new(_("Add Flatpak Remote")) - dialog.set_close_response("cancel") - dialog.add_response("cancel", _("Cancel")) - dialog.add_response("continue", _("Add")) - dialog.set_response_enabled("continue", False) - dialog.set_response_appearance("continue", Adw.ResponseAppearance.SUGGESTED) - - def name_update(widget): - is_enabled = True - self.name_to_add = widget.get_text() - name_pattern = re.compile(r"^[a-zA-Z0-9\-._]+$") - if not name_pattern.match(self.name_to_add): - is_enabled = False - - if is_enabled: - widget.remove_css_class("error") - else: - widget.add_css_class("error") - - if len(self.name_to_add) == 0: - is_enabled = False - - confirm_enabler(is_enabled) - - def url_update(widget): - is_enabled = True - self.url_to_add = widget.get_text() - url_pattern = re.compile(r"^[a-zA-Z0-9\-._~:/?#[\]@!$&\'()*+,;=]+$") - if not url_pattern.match(self.url_to_add): - is_enabled = False - - if is_enabled: - widget.remove_css_class("error") - else: - widget.add_css_class("error") - - if len(self.url_to_add) == 0: - is_enabled = False - - confirm_enabler(is_enabled) - - def confirm_enabler(is_enabled): - if len(self.name_to_add) == 0 or len(self.url_to_add) == 0: - is_enabled = False - dialog.set_response_enabled("continue", is_enabled) - - def set_user(widget): - self.add_as_user = widget.get_active() - - self.name_to_add = "" - self.url_to_add = "" - self.add_as_user = True - - info_box = Gtk.Box(orientation="vertical") - entry_list = Gtk.ListBox(selection_mode="none", margin_bottom=12) - entry_list.add_css_class("boxed-list") - - name_entry = Adw.EntryRow(title=_("Name")) - name_entry.set_text(name) - name_entry.connect("changed", name_update) - - url_entry = Adw.EntryRow(title=_("URL")) - url_entry.set_text(link) - url_entry.connect("changed", url_update) - - entry_list.append(name_entry) - entry_list.append(url_entry) - info_box.append(entry_list) - - install_type_list = Gtk.ListBox(selection_mode="none") - install_type_list.add_css_class("boxed-list") - - user_row = Adw.ActionRow( - title=_("User"), subtitle=_("Remote will be available to only you") - ) - user_check = Gtk.CheckButton(active=True) - user_check.connect("toggled", set_user) - user_row.add_prefix(user_check) - user_row.set_activatable_widget(user_check) - - system_row = Adw.ActionRow( - title=_("System"), - subtitle=_("Remote will be available to every user on the system"), - ) - system_check = Gtk.CheckButton() - system_row.add_prefix(system_check) - system_check.set_group(user_check) - system_row.set_activatable_widget(system_check) - - install_type_list.append(user_row) - install_type_list.append(system_row) - - info_box.append(install_type_list) - - dialog.set_extra_child(info_box) - dialog.connect("response", self.on_add_response, dialog.choose_finish, row) - dialog.present(self) - - if name != "": - name_update(name_entry) - if link != "": - url_update(url_entry) - - def add_remote_file_thread(self, filepath, system_or_user, name): - try: - subprocess.run( - [ - "flatpak-spawn", - "--host", - "flatpak", - "remote-add", - "--if-not-exists", - name, - filepath, - f"--{system_or_user}", - ], - capture_output=True, - check=True, - env=self.new_env, - ) - self.toast_overlay.add_toast( - Adw.Toast.new(_("{} successfully added").format(name)) - ) - except subprocess.CalledProcessError as e: - self.toast_overlay.add_toast( - Adw.Toast.new(_("Could not add {}").format(self.name_to_add)) - ) - print( - "error in remotes_window.addRemoteFromFileThread: could not add remote. error:", - e, - ) - - def add_remote_file(self, filepath): - def response(dialog, response, _a): - if response == "cancel": - self.should_pulse = False - return - - user_or_system = "user" - if system_check.get_active(): - user_or_system = "system" - - task = Gio.Task.new(None, None, self.addRemoteCallback) - task.run_in_thread( - lambda *_: self.add_remote_file_thread( - filepath, user_or_system, name_row.get_text() - ) - ) - - def name_update(widget): - is_enabled = True - self.name_to_add = widget.get_text() - name_pattern = re.compile(r"^[a-zA-Z\-]+$") - if not name_pattern.match(self.name_to_add): - is_enabled = False - - if is_enabled: - widget.remove_css_class("error") - else: - widget.add_css_class("error") - - if len(self.name_to_add) == 0: - is_enabled = False - - dialog.set_response_enabled("continue", is_enabled) - - self.should_pulse = True - - name = filepath.split("/") - name = name[len(name) - 1] - - dialog = Adw.AlertDialog.new(self, _("Add {}?").format(name)) - dialog.set_close_response("cancel") - dialog.add_response("cancel", _("Cancel")) - dialog.add_response("continue", _("Add")) - dialog.set_response_enabled("continue", False) - dialog.set_response_appearance("continue", Adw.ResponseAppearance.SUGGESTED) - dialog.connect("response", response, dialog.choose_finish) - - # Create Widgets - options_box = Gtk.Box(orientation="vertical") - options_list = Gtk.ListBox(selection_mode="none", margin_top=15) - name_row = Adw.EntryRow(title=_("Name")) - name_row.connect("changed", name_update) - user_row = Adw.ActionRow( - title=_("User"), subtitle=_("Remote will be available to only you") - ) - system_row = Adw.ActionRow( - title=_("System"), - subtitle=_("Remote will be available to every user on the system"), - ) - user_check = Gtk.CheckButton() - system_check = Gtk.CheckButton() - - # Apply Widgets - user_row.add_prefix(user_check) - user_row.set_activatable_widget(user_check) - system_row.add_prefix(system_check) - system_row.set_activatable_widget(system_check) - user_check.set_group(system_check) - options_list.append(name_row) - options_list.append(user_row) - options_list.append(system_row) - options_box.append(options_list) - dialog.set_extra_child(options_box) - - # Calls - user_check.set_active(True) - options_list.add_css_class("boxed-list") - dialog.present(self) - - def file_callback(self, object, result): - try: - file = object.open_finish(result) - self.add_remote_file(file.get_path()) - except GLib.GError: - pass - - def add_file_handler(self): - filter = Gtk.FileFilter(name=_("Flatpak Repos")) - filter.add_suffix("flatpakrepo") - filters = Gio.ListStore.new(Gtk.FileFilter) - filters.append(filter) - file_chooser = Gtk.FileDialog() - file_chooser.set_filters(filters) - file_chooser.set_default_filter(filter) - file_chooser.open(self.main_window, None, self.file_callback) - - def __init__(self, main_window, **kwargs): - super().__init__(**kwargs) - - # Create Variables - self.my_utils = myUtils(self) - self.host_remotes = [] - self.host_flatpaks = [] - self.main_window = main_window - self.new_env = dict(os.environ) - self.new_env["LC_ALL"] = "C" - self.should_pulse = False - - self.refresh.connect("clicked", lambda *_: self.generate_list()) - - self.add_from_file.add_suffix( - Gtk.Image.new_from_icon_name("right-large-symbolic") - ) - self.add_from_file.connect("activated", lambda *_: self.add_file_handler()) - self.custom_remote.add_suffix( - Gtk.Image.new_from_icon_name("right-large-symbolic") - ) - self.custom_remote.connect("activated", self.add_handler) - self.show_disabled_button.connect("clicked", lambda *_: self.generate_list()) - - # Calls - self.generate_list() - - def set_is_open_false(*args): - self.__class__.is_open = False - self.connect("closed", set_is_open_false) - if self.__class__.is_open: - return - else: - self.present(main_window) - self.__class__.is_open = True \ No newline at end of file diff --git a/src/search_install_window.py b/src/search_install_window.py deleted file mode 100644 index 7fe9983..0000000 --- a/src/search_install_window.py +++ /dev/null @@ -1,266 +0,0 @@ -from gi.repository import Gtk, Adw, GLib, Gdk, Gio -from .common import myUtils -import subprocess -import os -import pathlib - - -class RemoteRow(Adw.ActionRow): - def __init__(self, remote, **kwargs): - super().__init__(**kwargs) - my_utils = myUtils(self) - self.install_type = my_utils.get_install_type(remote[7]) - if self.install_type == "disabled": - self.set_visible(False) - return - self.set_activatable(True) - self.remote = remote - if remote[1] == "-": - self.set_title(remote[0]) - else: - self.set_title(remote[1]) - self.set_subtitle(_("{} wide").format(self.install_type)) - self.add_suffix(Gtk.Image.new_from_icon_name("right-large-symbolic")) - - -class ResultRow(Adw.ActionRow): - def __init__(self, flatpak, **kwargs): - super().__init__(**kwargs) - my_utils = myUtils(self) - name = flatpak[0] - description = flatpak[1] - app_id = flatpak[2] - version = flatpak[3] - self.flatpak = flatpak - self.set_title(GLib.markup_escape_text(f"{name}")) - self.set_subtitle(GLib.markup_escape_text(f"{app_id}\n{description}")) - self.check = Gtk.CheckButton() - self.check.add_css_class("selection-mode") - self.add_suffix( - Gtk.Label( - label=GLib.markup_escape_text(version), - wrap=True, - hexpand=True, - justify=Gtk.Justification.RIGHT, - ) - ) - self.add_suffix(self.check) - self.set_activatable_widget(self.check) - - -@Gtk.Template( - resource_path="/io/github/flattool/Warehouse/../data/ui/search_install.ui" -) -class SearchInstallWindow(Adw.Dialog): - __gtype_name__ = "SearchInstallWindow" - - nav_view = Gtk.Template.Child() - search_page = Gtk.Template.Child() - results_page = Gtk.Template.Child() - remotes_list = Gtk.Template.Child() - search_entry = Gtk.Template.Child() - blank_page = Gtk.Template.Child() - inner_stack = Gtk.Template.Child() - outer_stack = Gtk.Template.Child() - loading_page = Gtk.Template.Child() - results_scroll = Gtk.Template.Child() - results_list = Gtk.Template.Child() - too_many = Gtk.Template.Child() - action_bar = Gtk.Template.Child() - search_button = Gtk.Template.Child() - no_results = Gtk.Template.Child() - install_button = Gtk.Template.Child() - installing = Gtk.Template.Child() - installing_status = Gtk.Template.Child() - search_box = Gtk.Template.Child() - toast_overlay = Gtk.Template.Child() - progress_bar = Gtk.Template.Child() - - is_open = False - - def reset(self): - self.results = [] - self.results_list.remove_all() - self.inner_stack.set_visible_child(self.blank_page) - - def check_handler(self, button, row): - if button.get_active(): - self.selected.append(row.flatpak) - else: - self.selected.remove(row.flatpak) - if len(self.selected) == 0: - self.set_title(self.title) - self.action_bar.set_revealed(False) - else: - self.set_title(_("{} Selected").format(len(self.selected))) - self.action_bar.set_revealed(True) - - def generate_remotes_list(self): - total = 0 - for rem in self.host_remotes: - if self.my_utils.get_install_type(rem[7]) != "disabled": - total += 1 - if total < 2: - self.nav_view.push(self.results_page) - self.results_page.set_can_pop(False) - - for rem in self.host_remotes: - if self.my_utils.get_install_type(rem[7]) != "disabled": - self.search_remote = rem[0] - self.install_type = self.my_utils.get_install_type(rem[7]) - break - - if self.host_remotes[0][1] == "-": - self.title = _("Search {}").format(self.host_remotes[0][0]) - else: - self.title = _("Search {}").format(self.host_remotes[0][1]) - - self.set_title(self.title) - self.search_entry.set_placeholder_text( - _("Search {}").format(self.search_remote) - ) - self.search_entry.grab_focus() - return - - self.nav_view.connect("popped", lambda *_: self.set_title("")) - for remote in self.host_remotes: - row = RemoteRow(remote) - row.connect("activated", self.remote_choice) - self.remotes_list.append(row) - - def generate_results_list(self): - for pak in self.results: - row = ResultRow(pak) - row.check.set_active(row.flatpak in self.selected) - row.check.connect("toggled", self.check_handler, row) - row.set_tooltip_text(row.flatpak[2]) - if self.search_remote in row.flatpak[5].split(","): - self.results_list.append(row) - if self.results_list.get_row_at_index(0): - self.inner_stack.set_visible_child(self.results_scroll) - else: - self.inner_stack.set_visible_child(self.no_results) - - def remote_choice(self, row): - self.reset() - self.selected = [] - self.install_type = row.install_type - self.search_remote = row.remote[0] - self.search_entry.set_placeholder_text( - _("Search {}").format(self.search_remote) - ) - self.title = _("Search {}").format(row.get_title()) - self.set_title(self.title) - self.nav_view.push(self.results_page) - self.search_entry.grab_focus() - self.action_bar.set_revealed(len(self.selected) > 0) - - def search_handler(self, *args): - self.cancel_search.cancel() - self.reset() - self.inner_stack.set_visible_child(self.loading_page) - query = self.search_entry.get_text().strip() - if query == "": - self.inner_stack.set_visible_child(self.blank_page) - return - - def search_thread(*args): - command = [ - "flatpak-spawn", - "--host", - "flatpak", - "search", - "--columns=all", - query, - ] - output = ( - subprocess.run( - command, capture_output=True, text=True, env=self.new_env - ) - .stdout.strip() - .split("\n") - ) - for elm in output: - self.results.append(elm.split("\t")) - - def done(*args): - if len(self.results) > 50: - self.inner_stack.set_visible_child(self.too_many) - return - if ["No matches found"] in self.results: - self.inner_stack.set_visible_child(self.no_results) - return - self.generate_results_list() - - task = Gio.Task.new(None, self.cancel_search, done) - task.run_in_thread(search_thread) - - def install_handler(self, *args): - paks = [] - for pak in self.selected: - paks.append(pak[2]) - self.outer_stack.set_visible_child(self.installing) - self.set_title(_("Install From The Web")) - - def thread(*args): - self.my_utils.install_flatpak( - paks, - self.search_remote, - self.install_type, - self.progress_bar, - self.installing_status, - ) - - def done(*args): - self.main_window.refresh_list_of_flatpaks(None) - # Make window able to close - self.set_can_close(True) - if self.my_utils.install_success: - self.close() - self.main_window.toast_overlay.add_toast( - Adw.Toast.new(_("Installed successfully")) - ) - else: - self.progress_bar.set_visible(False) - self.nav_view.pop() - self.outer_stack.set_visible_child(self.nav_view) - self.toast_overlay.add_toast( - Adw.Toast.new(_("Some apps didn't install")) - ) - - # Make window unable to close - self.set_can_close(False) - task = Gio.Task.new(None, None, done) - task.run_in_thread(thread) - - def __init__(self, main_window, **kwargs): - super().__init__(**kwargs) - - # Create Variables - self.my_utils = myUtils(self) - self.new_env = dict(os.environ) - self.new_env["LC_ALL"] = "C" - self.host_remotes = self.my_utils.get_host_remotes() - self.main_window = main_window - self.results = [] - self.selected = [] - self.search_remote = "" - self.install_type = "" - self.title = _("Install From The Web") - - self.cancel_search = Gio.Cancellable() - self.search_entry.connect("activate", self.search_handler) - self.search_button.connect("clicked", self.search_handler) - self.install_button.connect("clicked", self.install_handler) - - # Apply Widgets - self.generate_remotes_list() - - def set_is_open_false(*args): - self.__class__.is_open = False - self.connect("closed", set_is_open_false) - if self.__class__.is_open: - return - else: - self.present(main_window) - self.__class__.is_open = True \ No newline at end of file diff --git a/src/snapshots_window.py b/src/snapshots_window.py deleted file mode 100644 index 62efcd3..0000000 --- a/src/snapshots_window.py +++ /dev/null @@ -1,281 +0,0 @@ -from gi.repository import Gtk, Adw, GLib, Gdk, Gio -from .common import myUtils -import subprocess -import os -import pathlib -import time - - -@Gtk.Template(resource_path="/io/github/flattool/Warehouse/../data/ui/snapshots.ui") -class SnapshotsWindow(Adw.Dialog): - __gtype_name__ = "SnapshotsWindow" - - new_env = dict(os.environ) - new_env["LC_ALL"] = "C" - host_home = str(pathlib.Path.home()) - user_data_path = host_home + "/.var/app/" - snapshots_path = ( - host_home + "/.var/app/io.github.flattool.Warehouse/data/Snapshots/" - ) - - snapshots_group = Gtk.Template.Child() - main_stack = Gtk.Template.Child() - no_snapshots = Gtk.Template.Child() - new_snapshot = Gtk.Template.Child() - open_folder_button = Gtk.Template.Child() - toast_overlay = Gtk.Template.Child() - outerbox = Gtk.Template.Child() - loading = Gtk.Template.Child() - loading_label = Gtk.Template.Child() - action_bar = Gtk.Template.Child() - - def show_list_or_empty(self): - # Make window able to close - self.set_can_close(True) - - self.action_bar.set_revealed(True) - if os.path.exists(self.snapshots_of_app_path): - if len(os.listdir(self.snapshots_of_app_path)) > 0: - self.main_stack.set_visible_child(self.outerbox) - return "list" - self.open_folder_button.set_sensitive(False) - self.main_stack.set_visible_child(self.no_snapshots) - return "empty" - - def generate_list(self): - if not os.path.exists(self.app_user_data): - self.new_snapshot.set_sensitive(False) - self.new_snapshot.set_tooltip_text(_("There is no User Data to Snapshot")) - - if self.show_list_or_empty() == "empty": - return - - snapshot_files = os.listdir(self.snapshots_of_app_path) - to_trash = [] - - for i in range(len(snapshot_files)): - if not snapshot_files[i].endswith(".tar.zst"): - # Find all files that aren't snapshots - to_trash.append(snapshot_files[i]) - - for i in range(len(to_trash)): - # Trash all files that aren't snapshots - a = self.my_utils.trash_folder(f"{self.snapshots_of_app_path}{to_trash[i]}") - if a == 0: - snapshot_files.remove(to_trash[i]) - - if len(snapshot_files) == 0: - self.main_stack.set_visible_child(self.no_snapshots) - return - - for i in range(len(snapshot_files)): - self.create_row(snapshot_files[i]) - - def create_row(self, file): - def size_thread(*args): - size = self.my_utils.get_size_with_format(self.snapshots_of_app_path + file) - GLib.idle_add(lambda *_a: row.set_subtitle(f"~{size}")) - - split_file = file.removesuffix(".tar.zst").split("_") - time = GLib.DateTime.new_from_unix_local(int(split_file[0])).format("%x %X") - row = Adw.ActionRow(title=time) - - task = Gio.Task() - task.run_in_thread(size_thread) - - label = Gtk.Label( - label=_("Version {}").format(split_file[1]), - hexpand=True, - wrap=True, - justify=Gtk.Justification.RIGHT, - ) - row.add_suffix(label) - - apply = Gtk.Button(icon_name="check-plain-symbolic", valign=Gtk.Align.CENTER) - apply.set_tooltip_text(_("Apply Snapshot")) - apply.connect("clicked", self.apply_snapshot, file, row) - apply.add_css_class("flat") - row.add_suffix(apply) - - trash = Gtk.Button(icon_name="user-trash-symbolic", valign=Gtk.Align.CENTER) - trash.set_tooltip_text(_("Trash Snapshot")) - trash.connect("clicked", self.trash_snapshot, file, row) - trash.add_css_class("flat") - row.add_suffix(trash) - self.snapshots_group.insert(row, 0) - self.main_stack.set_visible_child(self.outerbox) - self.open_folder_button.set_sensitive(True) - - def trash_snapshot(self, button, file, row): - def on_response(dialog, response, func): - if response == "cancel": - return - a = self.my_utils.trash_folder(self.snapshots_of_app_path + file) - if a == 0: - self.snapshots_group.remove(row) - if not self.snapshots_group.get_row_at_index(0): - self.my_utils.trash_folder(self.snapshots_of_app_path) - self.show_list_or_empty() - else: - self.toast_overlay.add_toast( - Adw.Toast.new(_("Could not trash snapshot")) - ) - - dialog = Adw.AlertDialog.new( - _("Trash Snapshot?"), - _("This snapshot and its contents will be sent to the trash."), - ) - dialog.add_response("cancel", _("Cancel")) - dialog.set_close_response("cancel") - dialog.add_response("continue", _("Trash Snapshot")) - dialog.set_response_appearance("continue", Adw.ResponseAppearance.DESTRUCTIVE) - dialog.connect("response", on_response, dialog.choose_finish) - dialog.present(self) - - def create_snapshot(self): - epoch = int(time.time()) - - def thread(): - response = self.my_utils.snapshot_apps( - epoch, - [self.snapshots_of_app_path], - [self.app_version], - [self.app_user_data], - ) - if response != 0: - GLib.idle_add( - self.toast_overlay.add_toast( - Adw.Toast.new(_("Could not create snapshot")) - ) - ) - return - if self.show_list_or_empty() == "list": - self.create_row(f"{epoch}_{self.app_version}.tar.zst") - - # Make window unable to close - self.set_can_close(False) - self.loading_label.set_label(_("Creating Snapshot…")) - self.action_bar.set_revealed(False) - self.main_stack.set_visible_child(self.loading) - - task = Gio.Task() - task.run_in_thread(lambda *_: thread()) - - def apply_snapshot(self, button, file, row): - self.applied = False - - def thread(): - try: - subprocess.run( - [ - "tar", - "--zstd", - "-xvf", - f"{self.snapshots_of_app_path}{file}", - "-C", - f"{self.app_user_data}", - ], - check=True, - env=self.new_env, - ) - self.applied = True - except subprocess.CalledProcessError as e: - print( - "error in snapshots_window.apply_snapshot.thread: CalledProcessError:", - e, - ) - - def callback(): - if not self.applied: - self.toast_overlay.add_toast( - Adw.Toast.new(_("Could not apply snapshot")) - ) - else: - self.toast_overlay.add_toast(Adw.Toast.new(_("Snapshot applied"))) - - self.new_snapshot.set_tooltip_text("") - self.show_list_or_empty() - - def on_response(dialog, response, func): - if response == "cancel": - return - to_apply = self.snapshots_of_app_path + file - to_trash = self.app_user_data - if os.path.exists(to_trash): - a = self.my_utils.trash_folder(to_trash) - if a != 0: - self.toast_overlay.add_toast( - Adw.Toast.new(_("Could not apply snapshot")) - ) - return - data = Gio.File.new_for_path(self.app_user_data) - data.make_directory() - if not os.path.exists(data.get_path()): - self.toast_overlay.add_toast( - Adw.Toast.new(_("Could not apply snapshot")) - ) - return - - # Make window unable to close - self.set_can_close(False) - self.loading_label.set_label(_("Applying Snapshot…")) - self.action_bar.set_revealed(False) - self.main_stack.set_visible_child(self.loading) - - task = Gio.Task.new(None, None, lambda *_: callback()) - task.run_in_thread(lambda *_: thread()) - - dialog = Adw.AlertDialog.new( - _("Apply Snapshot?"), - _("Applying this snapshot will trash any current user data for {}.").format( - self.app_name - ), - ) - dialog.add_response("cancel", _("Cancel")) - dialog.set_close_response("cancel") - dialog.add_response("continue", _("Apply Snapshot")) - dialog.set_response_appearance("continue", Adw.ResponseAppearance.DESTRUCTIVE) - dialog.connect("response", on_response, dialog.choose_finish) - dialog.present(self) - - def open_button_handler(self, widget, path): - try: - Gio.AppInfo.launch_default_for_uri(f"file://{path}", None) - except GLib.GError: - self.toast_overlay.add_toast(Adw.Toast.new(_("Could not open folder"))) - - def __init__(self, parent_window, flatpak_row, **kwargs): - super().__init__(**kwargs) - - # Variables - self.my_utils = myUtils(self) - self.app_name = flatpak_row[0] - self.app_id = flatpak_row[2] - self.app_version = flatpak_row[3] - self.app_ref = flatpak_row[8] - self.snapshots_of_app_path = self.snapshots_path + self.app_id + "/" - self.app_user_data = self.user_data_path + self.app_id + "/" - self.parent_window = parent_window - - if ( - self.app_version == "" - or self.app_version == "-" - or self.app_version == None - ): - self.app_version = 0.0 - - if not os.path.exists(self.snapshots_path): - # Create snapshots folder if none exists - file = Gio.File.new_for_path(self.snapshots_path) - file.make_directory() - - # Calls - self.generate_list() - self.open_folder_button.connect( - "clicked", self.open_button_handler, self.snapshots_of_app_path - ) - self.new_snapshot.connect("clicked", lambda *_: self.create_snapshot()) - - # Window stuffs - self.set_title(_("{} Snapshots").format(self.app_name)) - self.present(parent_window) diff --git a/src/warehouse.gresource.xml b/src/warehouse.gresource.xml index abbf591..403e1c6 100644 --- a/src/warehouse.gresource.xml +++ b/src/warehouse.gresource.xml @@ -1,15 +1,8 @@ - ../data/ui/window.ui - ../data/ui/orphans.ui - ../data/ui/filter.ui - ../data/ui/remotes.ui - ../data/ui/downgrade.ui - ../data/ui/search_install.ui - ../data/ui/snapshots.ui - ../data/ui/properties.ui ../data/style.css + main_window/window.ui gtk/help-overlay.ui diff --git a/src/warehouse.in b/src/warehouse.in index fdff899..7590638 100755 --- a/src/warehouse.in +++ b/src/warehouse.in @@ -42,5 +42,5 @@ if __name__ == '__main__': resource = Gio.Resource.load(os.path.join(pkgdatadir, 'warehouse.gresource')) resource._register() - from flattool_gui import main + from Warehouse import main sys.exit(main.main(VERSION)) diff --git a/src/window.py b/src/window.py deleted file mode 100644 index 6e54360..0000000 --- a/src/window.py +++ /dev/null @@ -1,970 +0,0 @@ -# window.py -# -# Copyright 2023 Heliguy -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, version 3 of the License only. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -# SPDX-License-Identifier: GPL-3.0-only - -import os -import pathlib -import subprocess -import re -import time - -from gi.repository import Adw, Gdk, Gio, GLib, Gtk -# from .properties_window import PropertiesWindow -from .filter_window import FilterWindow -from .common import myUtils -from .remotes_window import RemotesWindow -from .downgrade_window import DowngradeWindow -from .snapshots_window import SnapshotsWindow -from .const import Config - -from .app_row_widget import AppRow - - -@Gtk.Template(resource_path="/io/github/flattool/Warehouse/../data/ui/window.ui") -class WarehouseWindow(Adw.ApplicationWindow): - __gtype_name__ = "WarehouseWindow" - main_window_title = "Warehouse" - flatpaks_list_box = Gtk.Template.Child() - search_entry = Gtk.Template.Child() - search_button = Gtk.Template.Child() - search_bar = Gtk.Template.Child() - toast_overlay = Gtk.Template.Child() - no_flatpaks = Gtk.Template.Child() - no_results = Gtk.Template.Child() - main_stack = Gtk.Template.Child() - batch_mode_button = Gtk.Template.Child() - batch_mode_bar = Gtk.Template.Child() - batch_select_all_button = Gtk.Template.Child() - batch_uninstall_button = Gtk.Template.Child() - batch_clean_button = Gtk.Template.Child() - batch_copy_button = Gtk.Template.Child() - batch_snapshot_button = Gtk.Template.Child() - main_box = Gtk.Template.Child() - main_overlay = Gtk.Template.Child() - main_toolbar_view = Gtk.Template.Child() - filter_button = Gtk.Template.Child() - scrolled_window = Gtk.Template.Child() - main_menu = Gtk.Template.Child() - installing = Gtk.Template.Child() - uninstalling = Gtk.Template.Child() - snapshotting = Gtk.Template.Child() - loading_flatpaks = Gtk.Template.Child() - no_matches = Gtk.Template.Child() - reset_filters_button = Gtk.Template.Child() - uninstalling_status = Gtk.Template.Child() - refreshing = Gtk.Template.Child() - - main_progress_bar = Gtk.ProgressBar(visible=False, can_target=False) - main_progress_bar.add_css_class("osd") - clipboard = Gdk.Display.get_default().get_clipboard() - host_home = str(pathlib.Path.home()) - user_data_path = host_home + "/.var/app/" - in_batch_mode = False - should_select_all = False - host_flatpaks = None - install_success = True - no_close = None - re_get_flatpaks = False - currently_uninstalling = False - is_result = False - is_empty = False - total_selected = 0 - - def filter_func(self, row): - if (self.search_entry.get_text().lower() in row.get_title().lower()) or ( - self.search_entry.get_text().lower() in row.get_subtitle().lower() - ): - self.is_result = True - return True - - def uninstall_buttons_enable(self, should_enable): - if self.currently_uninstalling: - return - if not should_enable: - self.batch_uninstall_button.set_sensitive(False) - - def uninstall_flatpak_callback(self, _a, _b): - self.currently_uninstalling = False - self.refresh_list_of_flatpaks(_a) - self.main_toolbar_view.set_sensitive(True) - self.disconnect(self.no_close) - self.uninstall_buttons_enable(True) - self.main_stack.set_visible_child(self.main_box) - self.search_button.set_sensitive(True) - self.batch_actions_enable(False) - if self.my_utils.uninstall_success: - self.refresh_list_of_flatpaks(self) - self.toast_overlay.add_toast(Adw.Toast.new(_("Uninstalled successfully"))) - else: - self.toast_overlay.add_toast( - Adw.Toast.new(_("Could not uninstall some apps")) - ) - - def uninstall_flatpak_thread(self, ref_arr, id_arr, type_arr, should_trash): - self.my_utils.uninstall_flatpak( - ref_arr, - type_arr, - should_trash, - self.main_progress_bar, - self.uninstalling_status, - ) - - def uninstall_flatpak(self, should_trash): - ref_arr = [] - id_arr = [] - type_arr = [] - self.currently_uninstalling = True - i = 0 - while self.flatpaks_list_box.get_row_at_index(i) != None: - current = self.flatpaks_list_box.get_row_at_index(i) - if current.tickbox.get_active() == True: - ref_arr.append(current.app_ref) - id_arr.append(current.app_id) - type_arr.append(current.install_type) - i += 1 - self.set_title(self.main_window_title) - task = Gio.Task.new(None, None, self.uninstall_flatpak_callback) - task.run_in_thread( - lambda _task, _obj, _data, _cancellable, ref_arr=ref_arr, id_arr=id_arr, type_arr=type_arr, should_trash=should_trash: self.uninstall_flatpak_thread( - ref_arr, id_arr, type_arr, should_trash - ) - ) - - def batch_uninstall_button_handler(self, _widget): - has_user_data = False - - def batch_uninstall_response(_idk, response_id, _widget): - if response_id == "cancel": - return 1 - - try: - should_trash = trash_check.get_active() - except: - should_trash = False - - self.uninstall_buttons_enable(False) - - self.no_close = self.connect( - "close-request", lambda event: True - ) # Make window unable to close - self.main_stack.set_visible_child(self.uninstalling) - self.search_button.set_sensitive(False) - self.uninstall_flatpak(should_trash) - - # Create Widgets - dialog = Adw.AlertDialog.new( - _("Uninstall Selected Apps?"), - _("It will not be possible to use these apps after removal."), - ) - - # Check to see if at least one app in the list has user data - i = 0 - while True: - current = self.flatpaks_list_box.get_row_at_index(i) - i += 1 - if current == None: - break - if current.tickbox.get_active() and os.path.exists( - f"{self.user_data_path}{current.app_id}" - ): - has_user_data = True - break - - if has_user_data: - # Create Widgets - options_box = Gtk.Box(orientation="vertical") - header = Gtk.Label( - label=_("App Settings & Data"), halign="start", margin_top=10 - ) - options_list = Gtk.ListBox(selection_mode="none", margin_top=15) - keep_data = Adw.ActionRow( - title=_("Keep"), - subtitle=_("Allow restoring these apps' settings and content"), - ) - trash_data = Adw.ActionRow( - title=_("Trash"), - subtitle=_("Send these apps' settings and content to the trash"), - ) - keep_check = Gtk.CheckButton() - trash_check = Gtk.CheckButton() - - # Apply Widgets - keep_data.add_prefix(keep_check) - keep_data.set_activatable_widget(keep_check) - trash_data.add_prefix(trash_check) - trash_data.set_activatable_widget(trash_check) - keep_check.set_group(trash_check) - options_list.append(keep_data) - options_list.append(trash_data) - options_box.append(header) - options_box.append(options_list) - dialog.set_extra_child(options_box) - - # Calls - keep_check.set_active(True) - options_list.add_css_class("boxed-list") - header.add_css_class("heading") - header.add_css_class("h4") - - # Connections - dialog.connect("response", batch_uninstall_response, dialog.choose_finish) - - # Calls - dialog.set_close_response("cancel") - dialog.add_response("cancel", _("Cancel")) - dialog.add_response("continue", _("Uninstall")) - dialog.set_response_appearance("continue", Adw.ResponseAppearance.DESTRUCTIVE) - dialog.present(self) - - def uninstall_button_handler(self, row, name, ref, id): - if self.currently_uninstalling: - self.toast_overlay.add_toast( - Adw.Toast.new(_("Cannot uninstall while already uninstalling")) - ) - return - - def uninstall_response(_idk, response_id, _widget): - if response_id == "cancel": - return 1 - - try: - should_trash = trash_check.get_active() - except: - should_trash = False - - if response_id == "purge": - should_trash = True - - self.uninstall_buttons_enable(False) - - self.no_close = self.connect( - "close-request", lambda event: True - ) # Make window unable to close - self.main_stack.set_visible_child(self.uninstalling) - self.search_button.set_sensitive(False) - self.uninstall_flatpak(should_trash) - - row.tickbox.set_active(True) - - # Create Widgets - dialog = Adw.AlertDialog.new( - _("Uninstall {}?").format(name), - _("It will not be possible to use {} after removal.").format(name), - ) - - if os.path.exists(f"{self.user_data_path}{id}"): - # Create Widgets for Trash - options_box = Gtk.Box(orientation="vertical") - header = Gtk.Label( - label=_("App Settings & Data"), halign="start", margin_top=10 - ) - options_list = Gtk.ListBox(selection_mode="none", margin_top=15) - keep_data = Adw.ActionRow( - title=_("Keep"), - subtitle=_("Allow restoring this app's settings and content"), - ) - trash_data = Adw.ActionRow( - title=_("Trash"), - subtitle=_("Send this app's settings and content to the trash"), - ) - keep_check = Gtk.CheckButton(active=True) - trash_check = Gtk.CheckButton() - - # Apply Widgets for Trash - keep_data.add_prefix(keep_check) - keep_data.set_activatable_widget(keep_check) - trash_data.add_prefix(trash_check) - trash_data.set_activatable_widget(trash_check) - keep_check.set_group(trash_check) - dialog.set_extra_child(options_box) - options_list.append(keep_data) - options_list.append(trash_data) - options_box.append(header) - options_box.append(options_list) - options_list.add_css_class("boxed-list") - - dialog.set_close_response("cancel") - dialog.add_response("cancel", _("Cancel")) - dialog.add_response("continue", _("Uninstall")) - dialog.set_response_appearance("continue", Adw.ResponseAppearance.DESTRUCTIVE) - dialog.connect("response", uninstall_response, dialog.choose_finish) - dialog.present(self) - - def window_set_empty(self, is_empty): - self.batch_mode_button.set_sensitive(not is_empty) - self.search_button.set_sensitive(not is_empty) - self.filter_button.set_sensitive(not is_empty) - self.is_empty = is_empty - - if is_empty: - self.batch_mode_button.set_active(False) - self.main_stack.set_visible_child(self.no_flatpaks) - self.search_button.set_sensitive(False) - else: - self.main_stack.set_visible_child(self.main_box) - self.search_button.set_sensitive(True) - - def create_row(self, index): - row = AppRow(self, self.host_flatpaks, index) - if row.app_id == "io.github.flattool.Warehouse": - row.tickbox.set_sensitive(False) - self.flatpaks_list_box.insert(row, index) - - def generate_list_of_flatpaks(self): - self.host_flatpaks = self.my_utils.get_host_flatpaks() - self.dependent_runtimes = self.my_utils.get_dependent_runtimes() - self.set_title(self.main_window_title) - self.eol_list = [] - self.system_mask_list = self.my_utils.get_host_masks("system") - self.user_mask_list = self.my_utils.get_host_masks("user") - - for index in range(len(self.host_flatpaks)): - try: - if "eol" in self.host_flatpaks[index][12]: - self.eol_list.append(self.host_flatpaks[index][8]) - except: - print("Could not find EOL") - - for index in range(len(self.host_flatpaks)): - self.create_row(index) - - # self.windowSetEmpty(not self.flatpaks_list_box.get_row_at_index(0)) - self.batch_actions_enable(False) - self.main_stack.set_visible_child(self.main_box) - self.apply_filter() - - # Stop list window from opening with the list at the bottom by focusing the first visible row item - for index in range(len(self.host_flatpaks)): - if self.flatpaks_list_box.get_row_at_index(index).is_visible(): - self.flatpaks_list_box.get_row_at_index(index).grab_focus() - break - - def refresh_list_of_flatpaks(self, widget): - if self.currently_uninstalling: - return - - # I hate this so much... - def callback(*args): - self.flatpaks_list_box.remove_all() - self.generate_list_of_flatpaks() - self.batch_mode_button.set_active(False) - self.total_selected = 0 - - def runner(*args): - import time - time.sleep(0.2) - - self.main_stack.set_visible_child(self.refreshing) - task = Gio.Task.new(None, None, callback) - task.run_in_thread(runner) - - def reset_filters(self): - settings = Gio.Settings.new("io.github.flattool.Warehouse.filter") - for key in settings.list_keys(): - settings.reset(key) - self.apply_filter() - - def apply_filter(self): - self.batch_mode_button.set_active(False) - settings = Gio.Settings.new("io.github.flattool.Warehouse.filter") - show_apps = settings.get_boolean("show-apps") - show_runtimes = settings.get_boolean("show-runtimes") - remotes_list = settings.get_string("remotes-list").split(",") - runtimes_list = settings.get_string("runtimes-list").split(",") - total_visible = 0 - i = 0 - while self.flatpaks_list_box.get_row_at_index(i) != None: - current = self.flatpaks_list_box.get_row_at_index(i) - id = current.app_id - dependant = current.dependent_runtime - remote = f"{current.origin_remote}<>{current.install_type};" - is_runtime = current.is_runtime - - visible = True - if (not show_apps) and (not is_runtime): - visible = False - - if (not show_runtimes) and is_runtime: - visible = False - - if (not "all" in remotes_list) and (not remote in remotes_list): - visible = False - - if (not "all" in runtimes_list) and (not dependant in runtimes_list): - visible = False - - current.set_is_visible(visible) - total_visible += visible - i += 1 - if (total_visible == 0) or (runtimes_list != ["all"] and show_runtimes): - self.window_set_empty(True) - self.main_stack.set_visible_child(self.no_matches) - else: - self.window_set_empty(False) - self.main_stack.set_visible_child(self.main_box) - - def open_data_folder(self, path): - try: - Gio.AppInfo.launch_default_for_uri(f"file://{path}", None) - except GLib.GError: - self.toast_overlay.add_toast(Adw.Toast.new(_("Could not open folder"))) - - def trash_data(self, name, id, index): - def on_continue(dialog, response): - if response == "cancel": - return - result = self.my_utils.trash_folder(f"{self.user_data_path}{id}") - if result != 0: - self.toast_overlay.add_toast( - Adw.Toast.new(_("Could not trash user data")) - ) - return - self.lookup_action(f"open-data{index}").set_enabled(False) - self.lookup_action(f"trash{index}").set_enabled(False) - self.toast_overlay.add_toast(Adw.Toast.new(_("Trashed user data"))) - - dialog = Adw.AlertDialog.new( - _("Send {}'s User Data to the Trash?").format(name) - ) - dialog.set_body( - _("Your files and data for this app will be sent to the trash.") - ) - dialog.add_response("cancel", _("Cancel")) - dialog.set_close_response("cancel") - dialog.add_response("continue", _("Trash Data")) - dialog.set_response_appearance("continue", Adw.ResponseAppearance.DESTRUCTIVE) - dialog.connect("response", on_continue) - dialog.present(self) - - def mask_flatpak(self, row): - is_masked = ( - row.mask_label.get_visible() - ) # Check the visibility of the mask label to see if the flatpak is masked - result = [] - - def callback(): - if result[0] == 1: - self.toast_overlay.add_toast( - Adw.Toast.new( - _("Could not disable updates for {}").format(row.app_name) - ) - ) - return - row.set_masked(not is_masked) - self.lookup_action(f"mask{row.index}").set_enabled(is_masked) - self.lookup_action(f"unmask{row.index}").set_enabled(not is_masked) - - def on_continue(dialog, response): - if response == "cancel": - return - task = Gio.Task.new(None, None, lambda *_: callback()) - task.run_in_thread( - lambda *_: result.append( - self.my_utils.mask_flatpak(row.app_id, row.install_type, is_masked) - ) - ) - - if is_masked: - on_continue(self, None) - else: - dialog = Adw.AlertDialog.new( - _("Disable Updates for {}?").format(row.app_name) - ) - dialog.set_body( - _( - "This will mask {} ensuring it will never recieve any feature or security updates." - ).format(row.app_name) - ) - dialog.add_response("cancel", _("Cancel")) - dialog.set_close_response("cancel") - dialog.add_response("continue", _("Disable Updates")) - dialog.connect("response", on_continue) - dialog.set_response_appearance("continue", Adw.ResponseAppearance.DESTRUCTIVE) - dialog.present(self) - - def pin_flatpak(self, row): - def thread(*args): - command = f"flatpak-spawn --host flatpak pin --{row.install_type} runtime/{row.app_ref}" - if row.is_pinned: - command += " --remove" - response = subprocess.run( - command, - capture_output=True, - text=True, - shell=True - ).stderr - if response != "" and row.is_pinned: - GLib.idle_add(self.toast_overlay.add_toast(Adw.Toast.new(_("Could not enable auto removal")))) - return - elif response != "": - GLib.idle_add(self.toast_overlay.add_toast(Adw.Toast.new(_("Could not disable auto removal")))) - return - row.is_pinned = not row.is_pinned - GLib.idle_add(lambda *_, row=row: self.lookup_action(f"pin{row.index}").set_enabled(not row.is_pinned)) - GLib.idle_add(lambda *_, row=row: self.lookup_action(f"unpin{row.index}").set_enabled(row.is_pinned)) - GLib.idle_add(lambda *_, row=row: row.pin_label.set_visible(row.is_pinned)) - GLib.idle_add(lambda *_, row=row: row.info_button_show_or_hide()) - - def callback(*args): - print("done") - - def on_continue(dialog, response): - if response == "cancel": - return - - task = Gio.Task.new(None, None, None) - task.run_in_thread(thread) - - if row.is_pinned: - on_continue(self, None) - else: - dialog = Adw.AlertDialog.new( - _( - "Disable Automatic Removal for {}?" - ).format(row.app_name), - _( - "This will pin {} ensuring it well never be removed automatically, even if no app depends on it." - ).format(row.app_name), - ) - dialog.add_response("cancel", _("Cancel")) - dialog.set_close_response("cancel") - dialog.connect("response", on_continue) - dialog.add_response("continue", _("Disable Auto Removal")) - dialog.set_response_appearance("continue", Adw.ResponseAppearance.SUGGESTED) - dialog.present(self) - - def copy_item(self, to_copy, to_toast=None): - self.clipboard.set(to_copy) - if to_toast: - self.toast_overlay.add_toast(Adw.Toast.new(to_toast)) - - def run_callback(self, _a, _b): - if not self.my_utils.run_app_error: - return - - error = self.my_utils.run_app_error_message - dialog = Adw.AlertDialog.new(_("Could not Run App"), error) - copy_button = Gtk.Button( - label=_("Copy"), halign=Gtk.Align.CENTER, margin_top=12 - ) - copy_button.add_css_class("pill") - copy_button.add_css_class("suggested-action") - copy_button.connect("clicked", lambda *_: self.clipboard.set(error)) - dialog.set_extra_child(copy_button) - dialog.add_response("ok", _("OK")) - dialog.set_close_response("ok") - dialog.present(self) - - def run_app_thread(self, ref, to_toast=None): - self.run_app_error = False - task = Gio.Task.new(None, None, self.run_callback) - task.run_in_thread(lambda *_: self.my_utils.run_app(ref)) - if to_toast: - self.toast_overlay.add_toast(Adw.Toast.new(to_toast)) - - def batch_mode_handler(self, widget): - batch_mode = widget.get_active() - i = 0 - while self.flatpaks_list_box.get_row_at_index(i) != None: - current = self.flatpaks_list_box.get_row_at_index(i) - current.set_selectable(batch_mode) - i += 1 - self.in_batch_mode = batch_mode - self.batch_mode_bar.set_revealed(batch_mode) - - if not widget.get_active(): - self.batch_select_all_button.set_active(False) - - def key_handler(self, controller, keyval, keycode, state): - if keyval == Gdk.KEY_w and state == Gdk.ModifierType.CONTROL_MASK: - self.close() - if keyval == Gdk.KEY_Escape: - self.batch_mode_button.set_active(False) - - def batch_actions_enable(self, should_enable): - self.batch_copy_button.set_sensitive(should_enable) - self.batch_clean_button.set_sensitive(should_enable) - self.batch_snapshot_button.set_sensitive(should_enable) - if not self.currently_uninstalling: - self.batch_uninstall_button.set_sensitive(should_enable) - - def on_batch_clean_response(self, dialog, response, _a): - if response == "cancel": - return - i = 0 - trashReturnCodes = 0 - while True: - current = self.flatpaks_list_box.get_row_at_index(i) - i += 1 - if current == None: - break - if current.tickbox.get_active() == False: - continue - trash = self.my_utils.trash_folder(f"{self.user_data_path}{current.app_id}") - if trash == 1: - self.toast_overlay.add_toast( - Adw.Toast.new(_("{} has no data to trash").format(current.app_name)) - ) - continue - if trash == 2: - self.toast_overlay.add_toast( - Adw.Toast.new( - _("Could not trash {}'s data").format(current.app_name) - ) - ) - continue - self.lookup_action(f"open-data{current.index}").set_enabled( - False - ) # Disable the Open User Data dropdown option when the data was deleted - self.lookup_action(f"trash{current.index}").set_enabled( - False - ) # Disable the Trash User Data dropdown option when the data was deleted - self.batch_actions_enable(False) - self.batch_mode_button.set_active(False) - - def batch_clean_handler(self, widget): - dialog = Adw.AlertDialog.new( - _("Trash Selected Apps' User Data?"), - _("Your files and data for these apps will be sent to the trash."), - ) - dialog.set_close_response("cancel") - dialog.add_response("cancel", _("Cancel")) - dialog.add_response("continue", _("Trash Data")) - dialog.set_response_appearance("continue", Adw.ResponseAppearance.DESTRUCTIVE) - dialog.connect("response", self.on_batch_clean_response, dialog.choose_finish) - dialog.present(self) - - def select_all_handler(self, widget): - self.set_select_all(widget.get_active()) - - def batch_snapshot_handler(self, widget): - def batch_snapshot_response(dialog, response, _a): - if response == "cancel": - return - i = 0 - snapshots_path = ( - self.host_home - + "/.var/app/io.github.flattool.Warehouse/data/Snapshots/" - ) - snapshot_arr = [] - app_ver_arr = [] - app_data_arr = [] - epoch = int(time.time()) - self.no_close = self.connect( - "close-request", lambda event: True - ) # Make window unable to close - while self.flatpaks_list_box.get_row_at_index(i) != None: - current = self.flatpaks_list_box.get_row_at_index(i) - i += 1 - if current.tickbox.get_active() == False: - continue - if not os.path.exists(f"{self.user_data_path}{current.app_id}"): - continue - snapshot_arr.append(snapshots_path + current.app_id + "/") - app_ver_arr.append(current.app_version) - app_data_arr.append(f"{self.user_data_path}{current.app_id}") - - def thread(): - capture = self.my_utils.snapshot_apps( - epoch, - snapshot_arr, - app_ver_arr, - app_data_arr, - self.main_progress_bar, - ) - if capture != 0: - GLib.idle_add( - lambda *_: self.toast_overlay.add_toast( - Adw.Toast.new(_("Could not snapshot some apps")) - ) - ) - - def callback(*args): - self.main_stack.set_visible_child(self.main_box) - self.disconnect(self.no_close) - self.search_button.set_sensitive(True) - - self.search_button.set_sensitive(False) - self.batch_actions_enable(False) - self.batch_mode_button.set_active(False) - self.main_stack.set_visible_child(self.snapshotting) - task = Gio.Task.new(None, None, callback) - task.run_in_thread(lambda *_: thread()) - - dialog = Adw.AlertDialog.new( - _("Create Snapshots?"), - _( - "Snapshots are backups of the app's user data. They can be reapplied at any time. This could take a while." - ), - ) - dialog.set_close_response("cancel") - dialog.add_response("cancel", _("Cancel")) - dialog.add_response("continue", _("Create Snapshots")) - dialog.connect("response", batch_snapshot_response, dialog.choose_finish) - dialog.set_response_appearance("continue", Adw.ResponseAppearance.SUGGESTED) - dialog.present(self) - - def set_select_all(self, should_select_all): - i = 0 - while self.flatpaks_list_box.get_row_at_index(i) != None: - current = self.flatpaks_list_box.get_row_at_index(i) - if (current.get_visible() == True) and ( - current.app_id != "io.github.flattool.Warehouse" - ): - current.tickbox.set_active(should_select_all) - i += 1 - - def row_select_handler(self, tickbox): - if tickbox.get_active() == True: - self.total_selected += 1 - else: - self.total_selected -= 1 - - if self.total_selected == 0: - buttons_enable = False - self.set_title(self.main_window_title) - self.batch_actions_enable(False) - else: - self.set_title(f"{self.total_selected} Selected") - self.batch_actions_enable(True) - - def create_action(self, name, callback, shortcuts=None): - """Add a window action. - - Args: - name: the name of the action - callback: the function to be called when the action is - activated - shortcuts: an optional list of accelerators - """ - action = Gio.SimpleAction.new(name, None) - action.connect("activate", callback) - self.add_action(action) - if shortcuts: - self.set_accels_for_action(f"app.{name}", shortcuts) - - def copy_names(self, widget, _a): - to_copy = "" - i = 0 - while True: - current = self.flatpaks_list_box.get_row_at_index(i) - i += 1 - if current == None: - break - if current.tickbox.get_active(): - to_copy += f"{current.app_name}\n" - self.clipboard.set(to_copy) - self.toast_overlay.add_toast(Adw.Toast.new(_("Copied selected app names"))) - self.batch_actions_enable(False) - self.batch_mode_button.set_active(False) - - def copy_IDs(self, widget, _a): - to_copy = "" - i = 0 - while True: - current = self.flatpaks_list_box.get_row_at_index(i) - i += 1 - if current == None: - break - if current.tickbox.get_active(): - to_copy += f"{current.app_id}\n" - self.clipboard.set(to_copy) - self.toast_overlay.add_toast(Adw.Toast.new(_("Copied selected app IDs"))) - self.batch_actions_enable(False) - self.batch_mode_button.set_active(False) - - def copy_refs(self, widget, _a): - to_copy = "" - i = 0 - while True: - current = self.flatpaks_list_box.get_row_at_index(i) - i += 1 - if current == None: - break - if current.tickbox.get_active(): - to_copy += f"{current.app_ref}\n" - self.clipboard.set(to_copy) - self.toast_overlay.add_toast(Adw.Toast.new(_("Copied selected app refs"))) - self.batch_actions_enable(False) - self.batch_mode_button.set_active(False) - - def install_callback(self, _a, _b): - self.main_stack.set_visible_child(self.main_box) - self.search_button.set_sensitive(True) - if self.my_utils.install_success: - self.refresh_list_of_flatpaks(self) - self.toast_overlay.add_toast(Adw.Toast.new(_("Installed successfully"))) - else: - self.toast_overlay.add_toast(Adw.Toast.new(_("Could not install app"))) - - def install_thread(self, filepath, user_or_system): - self.my_utils.install_flatpak( - [filepath], None, user_or_system, self.main_progress_bar - ) - - def install_file(self, filepath): - def response(dialog, response, _a): - if response == "cancel": - return - - self.main_stack.set_visible_child(self.installing) - self.search_button.set_sensitive(False) - user_or_system = "user" - if system_check.get_active(): - user_or_system = "system" - - task = Gio.Task.new(None, None, self.install_callback) - task.run_in_thread(lambda *_: self.install_thread(filepath, user_or_system)) - - name = filepath.split("/") - name = name[len(name) - 1] - - dialog = Adw.AlertDialog.new(_("Install {}?").format(name)) - dialog.set_close_response("cancel") - dialog.add_response("cancel", _("Cancel")) - dialog.add_response("continue", _("Install")) - dialog.set_response_appearance("continue", Adw.ResponseAppearance.SUGGESTED) - dialog.connect("response", response, dialog.choose_finish) - - # Create Widgets - options_box = Gtk.Box(orientation="vertical") - options_list = Gtk.ListBox(selection_mode="none", margin_top=15) - user_row = Adw.ActionRow( - title=_("User"), subtitle=_("The app will be available to only you") - ) - system_row = Adw.ActionRow( - title=_("System"), - subtitle=_("The app will be available to every user on the system"), - ) - user_check = Gtk.CheckButton() - system_check = Gtk.CheckButton() - - # Apply Widgets - user_row.add_prefix(user_check) - user_row.set_activatable_widget(user_check) - system_row.add_prefix(system_check) - system_row.set_activatable_widget(system_check) - user_check.set_group(system_check) - options_list.append(user_row) - options_list.append(system_row) - options_box.append(options_list) - dialog.set_extra_child(options_box) - - # Calls - user_check.set_active(True) - options_list.add_css_class("boxed-list") - dialog.present(self) - - def drop_callback(self, target, _x, _y, _data): - filepath = target.get_value().get_path() - if filepath.endswith(".flatpak") or filepath.endswith(".flatpakref"): - self.install_file(filepath) - elif filepath.endswith(".flatpakrepo"): - remotes_window = RemotesWindow(self) - remotes_window.present() - remotes_window.add_remote_file(filepath) - else: - self.toast_overlay.add_toast(Adw.Toast.new(_("File type not supported"))) - - def on_invalidate(self, row): - if self.is_empty: - self.batch_mode_button.set_active(False) - self.main_stack.set_visible_child(self.no_flatpaks) - self.search_button.set_sensitive(False) - else: - self.main_stack.set_visible_child(self.main_box) - self.search_button.set_sensitive(True) - - self.is_result = False - self.flatpaks_list_box.invalidate_filter() - if self.is_result == False: - self.main_stack.set_visible_child(self.no_results) - self.search_button.set_sensitive(False) - - def on_change(self, prop, prop2): - if self.search_bar.get_search_mode() == False: - if self.is_empty: - self.batch_mode_button.set_active(False) - self.main_stack.set_visible_child(self.no_flatpaks) - self.search_button.set_sensitive(False) - else: - self.main_stack.set_visible_child(self.main_box) - self.search_button.set_sensitive(True) - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.my_utils = myUtils(self) - self.set_size_request(360, 360) - self.settings = Gio.Settings.new("io.github.flattool.Warehouse") - self.settings.bind( - "window-width", self, "default-width", Gio.SettingsBindFlags.DEFAULT - ) - self.settings.bind( - "window-height", self, "default-height", Gio.SettingsBindFlags.DEFAULT - ) - self.settings.bind( - "is-maximized", self, "maximized", Gio.SettingsBindFlags.DEFAULT - ) - self.settings.bind( - "is-fullscreen", self, "fullscreened", Gio.SettingsBindFlags.DEFAULT - ) - - self.system_pins = self.my_utils.get_host_system_pins() - self.user_pins = self.my_utils.get_host_user_pins() - - self.new_env = dict(os.environ) - self.new_env["LC_ALL"] = "C" - - if self.host_flatpaks == [["", ""]]: - self.window_set_empty(True) - return - - self.flatpaks_list_box.set_filter_func(self.filter_func) - - task = Gio.Task() - task.run_in_thread( - lambda *_: GLib.idle_add(lambda *_: self.generate_list_of_flatpaks()) - ) - - self.search_entry.connect("search-changed", self.on_invalidate) - self.search_bar.connect_entry(self.search_entry) - self.search_bar.connect("notify", self.on_change) - self.filter_button.connect("clicked", lambda *_: FilterWindow(self)) - self.batch_mode_button.connect("toggled", self.batch_mode_handler) - self.batch_clean_button.connect("clicked", self.batch_clean_handler) - self.batch_uninstall_button.connect( - "clicked", self.batch_uninstall_button_handler - ) - self.batch_select_all_button.connect("clicked", self.select_all_handler) - self.batch_snapshot_button.connect("clicked", self.batch_snapshot_handler) - self.reset_filters_button.connect("clicked", lambda *_: self.reset_filters()) - self.batch_actions_enable(False) - event_controller = Gtk.EventControllerKey() - event_controller.connect("key-pressed", self.key_handler) - self.add_controller(event_controller) - self.main_overlay.add_overlay(self.main_progress_bar) - - self.create_action("copy-names", self.copy_names) - self.create_action("copy-ids", self.copy_IDs) - self.create_action("copy-refs", self.copy_refs) - - file_drop = Gtk.DropTarget.new(Gio.File, Gdk.DragAction.COPY) - file_drop.connect("drop", self.drop_callback) - self.scrolled_window.add_controller(file_drop) - - if Config.DEVEL: - self.add_css_class("devel")