mirror of
https://github.com/morgan9e/warehouse
synced 2026-04-14 00:04:08 +09:00
Seperation and improvements
Seperate the orphans window into its own file, seperate non window related functions into a common file, minor UI improvements
This commit is contained in:
101
src/common.py
101
src/common.py
@@ -8,6 +8,8 @@ class myUtils:
|
||||
self.main_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
|
||||
|
||||
def trashFolder(self, path):
|
||||
if not os.path.exists(path):
|
||||
@@ -18,6 +20,9 @@ class myUtils:
|
||||
except subprocess.CalledProcessError:
|
||||
return 2
|
||||
|
||||
def getSizeWithFormat(self, path):
|
||||
return self.getSizeFormat(self.getDirectorySize(path))
|
||||
|
||||
def getSizeFormat(self, b):
|
||||
factor = 1024
|
||||
suffix = "B"
|
||||
@@ -68,4 +73,98 @@ class myUtils:
|
||||
else:
|
||||
image = Gtk.Image.new_from_icon_name("application-x-executable-symbolic")
|
||||
image.set_icon_size(Gtk.IconSize.LARGE)
|
||||
return image
|
||||
return image
|
||||
|
||||
def getHostRemotes(self):
|
||||
output = subprocess.run(["flatpak-spawn", "--host", "flatpak", "remotes", "--columns=all"], 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)
|
||||
return data
|
||||
|
||||
def getHostFlatpaks(self):
|
||||
output = subprocess.run(["flatpak-spawn", "--host", "flatpak", "list", "--columns=all"], 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)
|
||||
return data
|
||||
|
||||
def uninstallFlatpak(self, ref_arr, type_arr, should_trash):
|
||||
self.uninstall_success = True
|
||||
|
||||
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:
|
||||
subprocess.run(command, capture_output=False, check=True)
|
||||
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)
|
||||
subprocess.run(pk_command, capture_output=False, check=True)
|
||||
except subprocess.CalledProcessError:
|
||||
self.uninstall_success = False
|
||||
|
||||
if should_trash:
|
||||
host_paks = self.getHostFlatpaks()
|
||||
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.trashFolder(f"{self.user_data_path}{apps[i][1]}")
|
||||
|
||||
def installFlatpak(self, app_arr, remote, user_or_system):
|
||||
self.install_success = True
|
||||
fails = []
|
||||
|
||||
for i in range(len(app_arr)):
|
||||
command = ['flatpak-spawn', '--host', 'flatpak', 'install', remote, f"--{user_or_system}", '-y', app_arr[i]]
|
||||
try:
|
||||
subprocess.run(command, capture_output=False, check=True)
|
||||
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:
|
||||
subprocess.run(pk_command, capture_output=False, check=True)
|
||||
except subprocess.CalledProcessError:
|
||||
self.install_success = False
|
||||
|
||||
if (len(fails) > 0) and (user_or_system == "user"):
|
||||
self.install_success = False
|
||||
13
src/main.py
13
src/main.py
@@ -28,7 +28,7 @@ gi.require_version("Adw", "1")
|
||||
from gi.repository import Gtk, Gio, Adw, GLib
|
||||
from .window import WarehouseWindow
|
||||
from .remotes import RemotesWindow
|
||||
|
||||
from .orphans_window import OrphansWindow
|
||||
|
||||
class WarehouseApplication(Adw.Application):
|
||||
"""The main application singleton class."""
|
||||
@@ -42,7 +42,7 @@ class WarehouseApplication(Adw.Application):
|
||||
self.create_action("about", self.on_about_action)
|
||||
self.create_action("preferences", self.on_preferences_action)
|
||||
self.create_action("search", self.on_search_action, ["<primary>f"])
|
||||
self.create_action("manage-data-folders", self.on_manage_data_folders_action)
|
||||
self.create_action("manage-data-folders", self.manage_data_shortcut)
|
||||
self.create_action("toggle-batch-mode", self.batch_mode_shortcut, ["<primary>b", "<primary>Return"])
|
||||
self.create_action("select-all-in-batch-mode", self.select_all_shortcut, ["<primary>a"])
|
||||
self.create_action("manage-data-folders", self.manage_data_shortcut, ["<primary>d"])
|
||||
@@ -66,7 +66,8 @@ class WarehouseApplication(Adw.Application):
|
||||
self.props.active_window.batch_select_all_handler(select_button)
|
||||
|
||||
def manage_data_shortcut(self, widget, _):
|
||||
self.props.active_window.orphans_window()
|
||||
#self.props.active_window.orphans_window()
|
||||
OrphansWindow(self.props.active_window).present()
|
||||
|
||||
def refresh_list_shortcut(self, widget, _):
|
||||
self.props.active_window.refresh_list_of_flatpaks(widget, True)
|
||||
@@ -78,6 +79,10 @@ class WarehouseApplication(Adw.Application):
|
||||
def show_remotes_shortcut(self, widget, _):
|
||||
RemotesWindow(self.props.active_window).present()
|
||||
|
||||
# def on_manage_data_folders_action(self, widget, _):
|
||||
# #self.props.active_window.orphans_window()
|
||||
# OrphansWindow(self.props.active_window).present()
|
||||
|
||||
def do_activate(self):
|
||||
"""Called when the application is activated.
|
||||
|
||||
@@ -115,8 +120,6 @@ class WarehouseApplication(Adw.Application):
|
||||
def on_search_action(self, widget, _):
|
||||
self.props.active_window.search_bar.set_search_mode(not self.props.active_window.search_bar.get_search_mode())
|
||||
|
||||
def on_manage_data_folders_action(self, widget, _):
|
||||
self.props.active_window.orphans_window()
|
||||
|
||||
def on_show_runtimes_action(self, widget, _):
|
||||
self.show_runtimes_stateful.set_state(GLib.Variant.new_boolean(state := (not self.show_runtimes_stateful.get_property("state").get_boolean())))
|
||||
|
||||
@@ -6,6 +6,7 @@ blueprints = custom_target('blueprints',
|
||||
input: files(
|
||||
'gtk/help-overlay.blp',
|
||||
'window.blp',
|
||||
'orphans.blp',
|
||||
),
|
||||
output: '.',
|
||||
command: [find_program('blueprint-compiler'), 'batch-compile', '@OUTPUT@', '@CURRENT_SOURCE_DIR@', '@INPUT@'],
|
||||
@@ -44,6 +45,7 @@ warehouse_sources = [
|
||||
'orphans_window.py',
|
||||
'remotes.py',
|
||||
'common.py',
|
||||
'orphans.blp',
|
||||
]
|
||||
|
||||
install_data(warehouse_sources, install_dir: moduledir)
|
||||
|
||||
65
src/orphans.blp
Normal file
65
src/orphans.blp
Normal file
@@ -0,0 +1,65 @@
|
||||
using Gtk 4.0;
|
||||
using Adw 1;
|
||||
|
||||
template OrphansWindow : Adw.Window {
|
||||
default-width: 500;
|
||||
default-height: 450;
|
||||
|
||||
Adw.ToolbarView main_toolbar_view {
|
||||
[top]
|
||||
HeaderBar header_bar {
|
||||
// [start]
|
||||
// Button refresh_button {
|
||||
// icon-name: "view-refresh-symbolic";
|
||||
// tooltip-text: _("Refresh the List of Installed Apps");
|
||||
// }
|
||||
}
|
||||
content:
|
||||
Adw.ToastOverlay toast_overlay {
|
||||
Stack main_stack {
|
||||
Box main_box {
|
||||
orientation: vertical;
|
||||
Overlay main_overlay {
|
||||
ScrolledWindow scrolled_window {
|
||||
vexpand: true;
|
||||
Adw.Clamp{
|
||||
ListBox list_of_data {
|
||||
margin-top: 6;
|
||||
margin-bottom: 6;
|
||||
margin-start: 12;
|
||||
margin-end: 12;
|
||||
hexpand: true;
|
||||
valign: start;
|
||||
selection-mode: none;
|
||||
styles["boxed-list"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Adw.StatusPage no_data {
|
||||
icon-name: "check-plain-symbolic";
|
||||
title: _("No Leftover Data");
|
||||
description: _("There is no leftover user data");
|
||||
}
|
||||
}
|
||||
};
|
||||
[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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,214 @@
|
||||
from gi.repository import Gtk, Adw, GLib, Gdk, Gio
|
||||
from .common import myUtils
|
||||
import subprocess
|
||||
import os
|
||||
import pathlib
|
||||
|
||||
def show_orphans_window():
|
||||
pass # place holder until I properly seperate the files
|
||||
@Gtk.Template(resource_path="/io/github/heliguy4599/Warehouse/orphans.ui")
|
||||
class OrphansWindow(Adw.Window):
|
||||
__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()
|
||||
|
||||
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 = ""
|
||||
should_pulse = False
|
||||
no_close_id = 0
|
||||
|
||||
def key_handler(self, _a, event, _c, _d):
|
||||
if event == Gdk.KEY_Escape:
|
||||
self.close()
|
||||
|
||||
def pulser(self):
|
||||
if self.should_pulse:
|
||||
self.progress_bar.pulse()
|
||||
GLib.timeout_add(500, self.pulser)
|
||||
|
||||
def selectionHandler(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 selectAllHandler(self, button):
|
||||
self.should_select_all = button.get_active()
|
||||
self.generateList()
|
||||
|
||||
def installCallback(self, *_args):
|
||||
self.set_title(self.window_title)
|
||||
self.generateList()
|
||||
self.should_pulse = False
|
||||
self.progress_bar.set_visible(False)
|
||||
self.set_sensitive(True)
|
||||
self.app_window.refresh_list_of_flatpaks(self, False)
|
||||
self.disconnect(self.no_close_id) # Make window able to close
|
||||
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(_("Some apps didn't install")))
|
||||
|
||||
def installHandler(self):
|
||||
self.set_title(_("Installing... This could take a while"))
|
||||
task = Gio.Task.new(None, None, self.installCallback)
|
||||
task.run_in_thread(lambda _task, _obj, _data, _cancellable, id_list=self.selected_dirs, remote=self.selected_remote, app_type=self.selected_remote_type: self.my_utils.installFlatpak(id_list, remote, app_type))
|
||||
|
||||
def installButtonHandler(self, button):
|
||||
remote_select_buttons = []
|
||||
self.should_pulse = True
|
||||
self.pulser()
|
||||
|
||||
def remote_select_handler(button):
|
||||
if not button.get_active():
|
||||
return
|
||||
remote_index = remote_select_buttons.index(button)
|
||||
self.selected_remote = self.host_remotes[remote_index][0]
|
||||
self.selected_remote_type = self.host_remotes[remote_index][7]
|
||||
|
||||
def onResponse(dialog, response_id, _function):
|
||||
if response_id == "cancel":
|
||||
self.should_pulse = False
|
||||
return
|
||||
self.installHandler()
|
||||
self.progress_bar.set_visible(True)
|
||||
self.set_sensitive(False)
|
||||
self.no_close_id = self.connect("close-request", lambda event: True) # Make window unable to close
|
||||
|
||||
dialog = Adw.MessageDialog.new(self, _("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")
|
||||
|
||||
for i in range(len(self.host_remotes)):
|
||||
remote_row = Adw.ActionRow(title=self.host_remotes[i][1])
|
||||
label = Gtk.Label(label=_("{} wide").format(self.host_remotes[i][7]))
|
||||
remote_select = Gtk.CheckButton()
|
||||
remote_select_buttons.append(remote_select)
|
||||
remote_select.connect("toggled", remote_select_handler)
|
||||
remote_row.set_activatable_widget(remote_select)
|
||||
|
||||
if remote_row.get_title() == '-':
|
||||
remote_row.set_title(self.host_remotes[i][0])
|
||||
|
||||
if i > 0:
|
||||
remote_select.set_group(remote_select_buttons[i-1])
|
||||
|
||||
remote_row.add_prefix(remote_select)
|
||||
remote_row.add_suffix(label)
|
||||
remote_list.append(remote_row)
|
||||
|
||||
remote_select_buttons[0].set_active(True)
|
||||
|
||||
if len(self.host_remotes) > 1:
|
||||
dialog.set_extra_child(remotes_scroll)
|
||||
|
||||
dialog.connect("response", onResponse, dialog.choose_finish)
|
||||
dialog.present()
|
||||
|
||||
def trashHandler(self, button):
|
||||
|
||||
def onResponse(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.trashFolder(path)
|
||||
self.select_all_button.set_active(False)
|
||||
self.generateList()
|
||||
|
||||
dialog = Adw.MessageDialog.new(self, _("Trash folders?"), _("These folders will be moved to the trash."))
|
||||
dialog.connect("response", onResponse, 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()
|
||||
|
||||
# Create the list of folders in the window
|
||||
def generateList(self):
|
||||
self.host_flatpaks = self.my_utils.getHostFlatpaks()
|
||||
self.list_of_data.remove_all()
|
||||
self.selected_dirs = []
|
||||
self.set_title(self.window_title)
|
||||
self.should_pulse = False
|
||||
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)):
|
||||
id_list.append(self.host_flatpaks[i][2])
|
||||
|
||||
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)
|
||||
dir_row.set_subtitle(self.my_utils.getSizeWithFormat(self.user_data_path + dir_name))
|
||||
|
||||
select_button = Gtk.CheckButton()
|
||||
select_button.add_css_class("selection-mode")
|
||||
select_button.connect("toggled", self.selectionHandler, dir_name)
|
||||
select_button.set_active(self.should_select_all)
|
||||
dir_row.add_suffix(select_button)
|
||||
dir_row.set_activatable_widget(select_button)
|
||||
|
||||
# Add row to list
|
||||
self.list_of_data.append(dir_row)
|
||||
|
||||
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.getHostRemotes()
|
||||
self.host_flatpaks = self.my_utils.getHostFlatpaks()
|
||||
|
||||
self.progress_bar = Gtk.ProgressBar(visible=False, pulse_step=0.7)
|
||||
self.progress_bar.add_css_class("osd")
|
||||
self.app_window = main_window
|
||||
|
||||
self.set_modal(True)
|
||||
self.set_transient_for(main_window)
|
||||
self.set_size_request(260, 230)
|
||||
self.generateList()
|
||||
|
||||
event_controller = Gtk.EventControllerKey()
|
||||
event_controller.connect("key-pressed", self.key_handler)
|
||||
self.add_controller(event_controller)
|
||||
|
||||
self.install_button.connect("clicked", self.installButtonHandler)
|
||||
self.trash_button.connect("clicked", self.trashHandler)
|
||||
self.select_all_button.connect("toggled", self.selectAllHandler)
|
||||
self.main_overlay.add_overlay(self.progress_bar)
|
||||
@@ -1,5 +1,5 @@
|
||||
from gi.repository import Gtk, Adw, GLib, Gdk, Gio
|
||||
from .functions import functions
|
||||
from .common import myUtils
|
||||
import subprocess
|
||||
import os
|
||||
|
||||
@@ -25,7 +25,7 @@ def show_properties_window(widget, index, window):
|
||||
user_data_list.append(user_data_row)
|
||||
user_data_list.add_css_class("boxed-list")
|
||||
|
||||
func = functions(window)
|
||||
my_utils = myUtils(window)
|
||||
|
||||
def key_handler(_a, event, _c, _d):
|
||||
if event == Gdk.KEY_Escape:
|
||||
@@ -42,7 +42,7 @@ def show_properties_window(widget, index, window):
|
||||
def on_response(_a, response_id, _b):
|
||||
if response_id != "continue":
|
||||
return
|
||||
if func.trash_folder(data_folder) == 0:
|
||||
if my_utils.trashFolder(data_folder) == 0:
|
||||
properties_toast_overlay.add_toast(Adw.Toast.new(_("Trashed user data")))
|
||||
user_data_list.remove(user_data_row)
|
||||
user_data_list.append(Adw.ActionRow(title="No User Data"))
|
||||
@@ -70,7 +70,7 @@ def show_properties_window(widget, index, window):
|
||||
window.clipboard.set(to_copy)
|
||||
properties_toast_overlay.add_toast(Adw.Toast.new(_("Copied {}").format(title)))
|
||||
|
||||
image = func.find_app_icon(window.host_flatpaks[index][2])
|
||||
image = my_utils.findAppIcon(window.host_flatpaks[index][2])
|
||||
image.add_css_class("icon-dropshadow")
|
||||
image.set_margin_top(12)
|
||||
image.set_pixel_size(100)
|
||||
@@ -83,7 +83,7 @@ def show_properties_window(widget, index, window):
|
||||
|
||||
if os.path.exists(path):
|
||||
user_data_row.set_title("User Data")
|
||||
user_data_row.set_subtitle(f"{path}\n~{func.get_size_format(func.get_directory_size(path))}")
|
||||
user_data_row.set_subtitle(f"{path}\n~{my_utils.getSizeWithFormat(path)}")
|
||||
|
||||
open_button = Gtk.Button(icon_name="document-open-symbolic", valign=Gtk.Align.CENTER, tooltip_text=_("Open Data Folder"))
|
||||
open_button.add_css_class("flat")
|
||||
|
||||
@@ -61,7 +61,7 @@ class RemotesWindow(Adw.Window):
|
||||
def name_update(widget):
|
||||
is_enabled = True
|
||||
self.name_to_add = widget.get_text()
|
||||
name_pattern = re.compile(r'^[a-zA-Z]+$')
|
||||
name_pattern = re.compile(r'^[a-zA-Z\-]+$')
|
||||
if not name_pattern.match(self.name_to_add):
|
||||
is_enabled = False
|
||||
|
||||
@@ -157,15 +157,53 @@ class RemotesWindow(Adw.Window):
|
||||
self.generate_list()
|
||||
|
||||
def remove_handler(self, _widget, index):
|
||||
def remove_apps_check_handler(button):
|
||||
if button.get_active():
|
||||
apps_box.prepend(apps_scroll)
|
||||
apps_box.prepend(label)
|
||||
else:
|
||||
apps_box.remove(label)
|
||||
apps_box.remove(apps_scroll)
|
||||
name = self.host_remotes[index][0]
|
||||
title = self.host_remotes[index][1]
|
||||
install_type = self.host_remotes[index][7]
|
||||
dialog = Adw.MessageDialog.new(self, _("Remove {}?").format(name), _("Any installed apps from {} will stop receiving updates").format(name))
|
||||
|
||||
body_text = _("Any installed apps from {} will stop receiving updates").format(name)
|
||||
dialog = Adw.MessageDialog.new(self, _("Remove {}?").format(name), 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)
|
||||
|
||||
label = Gtk.Label(label=_("These apps will be uninstalled"))
|
||||
remove_apps = Gtk.CheckButton(label=_("Uninstall apps from this remote"))
|
||||
remove_apps.connect("toggled", remove_apps_check_handler)
|
||||
|
||||
height = 400
|
||||
apps_box = Gtk.Box(orientation="vertical")
|
||||
apps_scroll = Gtk.ScrolledWindow(vexpand=True, min_content_height=height, margin_top=6, margin_bottom=6)
|
||||
apps_list = Gtk.ListBox(selection_mode="none", valign="start")
|
||||
apps_list.add_css_class("boxed-list")
|
||||
apps_box.append(remove_apps)
|
||||
#apps_box.append(apps_scroll)
|
||||
|
||||
for i in range(len(self.host_flatpaks)):
|
||||
if self.host_flatpaks[i][6] != name:
|
||||
continue
|
||||
if self.host_flatpaks[i][7] != install_type:
|
||||
continue
|
||||
|
||||
app_row = Adw.ActionRow(title=self.host_flatpaks[i][0])
|
||||
apps_list.append(app_row)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
apps_scroll.set_child(apps_list)
|
||||
dialog.set_extra_child(apps_box)
|
||||
dialog.present()
|
||||
|
||||
def generate_list(self):
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<gresources>
|
||||
<gresource prefix="/io/github/heliguy4599/Warehouse">
|
||||
<file preprocess="xml-stripblanks">window.ui</file>
|
||||
<file preprocess="xml-stripblanks">orphans.ui</file>
|
||||
<file preprocess="xml-stripblanks">gtk/help-overlay.ui</file>
|
||||
</gresource>
|
||||
<gresource prefix="/io/github/heliguy4599/Warehouse/icons/scalable/actions/">
|
||||
|
||||
282
src/window.py
282
src/window.py
@@ -22,7 +22,7 @@ import subprocess
|
||||
|
||||
from gi.repository import Adw, Gdk, Gio, GLib, Gtk
|
||||
from .properties_window import show_properties_window
|
||||
from .orphans_window import show_orphans_window
|
||||
#from .orphans_window import show_orphans_window
|
||||
from .common import myUtils
|
||||
|
||||
@Gtk.Template(resource_path="/io/github/heliguy4599/Warehouse/window.ui")
|
||||
@@ -56,7 +56,6 @@ class WarehouseWindow(Adw.ApplicationWindow):
|
||||
in_batch_mode = False
|
||||
should_select_all = False
|
||||
host_flatpaks = None
|
||||
uninstall_success = True
|
||||
install_success = True
|
||||
should_pulse = True
|
||||
no_close = None
|
||||
@@ -76,7 +75,7 @@ class WarehouseWindow(Adw.ApplicationWindow):
|
||||
self.refresh_list_of_flatpaks(_a, False)
|
||||
self.main_toolbar_view.set_sensitive(True)
|
||||
self.disconnect(self.no_close)
|
||||
if self.uninstall_success:
|
||||
if self.my_utils.uninstall_success:
|
||||
if self.in_batch_mode:
|
||||
self.toast_overlay.add_toast(Adw.Toast.new(_("Uninstalled selected apps")))
|
||||
else:
|
||||
@@ -84,40 +83,22 @@ class WarehouseWindow(Adw.ApplicationWindow):
|
||||
else:
|
||||
self.toast_overlay.add_toast(Adw.Toast.new(_("Could not uninstall some apps")))
|
||||
|
||||
def uninstall_flatpak_thread(self, ref_arr, id_arr, should_trash):
|
||||
failures = []
|
||||
for i in range(len(ref_arr)):
|
||||
try:
|
||||
subprocess.run(["flatpak-spawn", "--host", "flatpak", "remove", "-y", ref_arr[i]], capture_output=False, check=True)
|
||||
except subprocess.CalledProcessError:
|
||||
failures.append(ref_arr[i])
|
||||
|
||||
if len(failures) > 0:
|
||||
pk_command = ["flatpak-spawn", "--host", "pkexec", "flatpak", "remove", "-y"]
|
||||
for i in range(len(failures)):
|
||||
pk_command.append(failures[i])
|
||||
try:
|
||||
subprocess.run(pk_command, capture_output=False, check=True)
|
||||
except subprocess.CalledProcessError:
|
||||
self.uninstall_success = False
|
||||
|
||||
if should_trash:
|
||||
for i in range(len(id_arr)):
|
||||
try:
|
||||
subprocess.run(["flatpak-spawn", "--host", "gio", "trash", f"{self.user_data_path}{id_arr[i]}"])
|
||||
except subprocess.CalledProcessError:
|
||||
self.toast_overlay.add_toast(Adw.Toast.new(_("Could not trash data")))
|
||||
def uninstall_flatpak_thread(self, ref_arr, id_arr, type_arr, should_trash):
|
||||
self.my_utils.uninstallFlatpak(ref_arr, type_arr, should_trash)
|
||||
|
||||
def uninstall_flatpak(self, index_arr, should_trash):
|
||||
ref_arr = []
|
||||
id_arr = []
|
||||
type_arr = []
|
||||
for i in range(len(index_arr)):
|
||||
ref = self.host_flatpaks[index_arr[i]][8]
|
||||
id = self.host_flatpaks[index_arr[i]][2]
|
||||
app_type = self.host_flatpaks[index_arr[i]][7]
|
||||
ref_arr.append(ref)
|
||||
id_arr.append(id)
|
||||
type_arr.append(app_type)
|
||||
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, should_trash=should_trash: self.uninstall_flatpak_thread(ref_arr, id_arr, should_trash))
|
||||
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):
|
||||
self.should_pulse = True
|
||||
@@ -180,253 +161,6 @@ class WarehouseWindow(Adw.ApplicationWindow):
|
||||
dialog.connect("response", uninstall_response, dialog.choose_finish)
|
||||
Gtk.Window.present(dialog)
|
||||
|
||||
def orphans_window(self):
|
||||
global window_title
|
||||
window_title = _("Manage Leftover Data")
|
||||
orphans_window = Adw.Window(title=window_title)
|
||||
orphans_clamp = Adw.Clamp()
|
||||
orphans_scroll = Gtk.ScrolledWindow()
|
||||
orphans_toast_overlay = Adw.ToastOverlay()
|
||||
orphans_stack = Gtk.Stack()
|
||||
orphans_overlay = Gtk.Overlay()
|
||||
orphans_progress_bar = Gtk.ProgressBar(visible=False, pulse_step=0.7)
|
||||
orphans_toolbar_view = Adw.ToolbarView()
|
||||
orphans_title_bar = Gtk.HeaderBar()
|
||||
orphans_action_bar = Gtk.ActionBar()
|
||||
orphans_list = Gtk.ListBox(selection_mode="none", valign=Gtk.Align.START, margin_top=6, margin_bottom=6, margin_start=12, margin_end=12)
|
||||
no_data = Adw.StatusPage(icon_name="check-plain-symbolic", title=_("No Data"), description=_("There is no leftover user data"))
|
||||
|
||||
orphans_window.set_default_size(500, 450)
|
||||
orphans_window.set_size_request(260, 230)
|
||||
orphans_window.set_modal(True)
|
||||
orphans_window.set_resizable(True)
|
||||
orphans_window.set_transient_for(self)
|
||||
|
||||
orphans_stack.add_child(orphans_overlay)
|
||||
orphans_toast_overlay.set_child(orphans_stack)
|
||||
orphans_progress_bar.add_css_class("osd")
|
||||
orphans_overlay.add_overlay(orphans_progress_bar)
|
||||
orphans_overlay.set_child(orphans_scroll)
|
||||
|
||||
orphans_toolbar_view.add_top_bar(orphans_title_bar)
|
||||
orphans_toolbar_view.add_bottom_bar(orphans_action_bar)
|
||||
orphans_toolbar_view.set_content(orphans_toast_overlay)
|
||||
orphans_window.set_content(orphans_toolbar_view)
|
||||
orphans_list.add_css_class("boxed-list")
|
||||
orphans_scroll.set_child(orphans_clamp)
|
||||
orphans_clamp.set_child(orphans_list)
|
||||
orphans_stack.add_child(no_data)
|
||||
|
||||
global total_selected
|
||||
total_selected = 0
|
||||
global selected_rows
|
||||
selected_rows = []
|
||||
should_pulse = False
|
||||
show_orphans_window()
|
||||
|
||||
def orphans_pulser():
|
||||
nonlocal should_pulse
|
||||
if should_pulse:
|
||||
orphans_progress_bar.pulse()
|
||||
GLib.timeout_add(500, orphans_pulser)
|
||||
|
||||
def toggle_button_handler(button):
|
||||
if button.get_active():
|
||||
generate_list(button, True)
|
||||
else:
|
||||
generate_list(button, False)
|
||||
|
||||
def generate_list(widget, is_select_all):
|
||||
global window_title
|
||||
orphans_window.set_title(window_title)
|
||||
global total_selected
|
||||
total_selected = 0
|
||||
global selected_rows
|
||||
selected_rows = []
|
||||
trash_button.set_sensitive(False)
|
||||
install_button.set_sensitive(False)
|
||||
|
||||
orphans_list.remove_all()
|
||||
file_list = os.listdir(self.user_data_path)
|
||||
id_list = []
|
||||
|
||||
for i in range(len(self.host_flatpaks)):
|
||||
id_list.append(self.host_flatpaks[i][2])
|
||||
|
||||
row_index = -1
|
||||
for i in range(len(file_list)):
|
||||
if not file_list[i] in id_list:
|
||||
row_index += 1
|
||||
select_orphans_tickbox = Gtk.CheckButton(halign=Gtk.Align.CENTER)
|
||||
orphans_row = Adw.ActionRow(title=GLib.markup_escape_text(file_list[i]), subtitle=_("~") + self.my_utils.getSizeFormat(self.my_utils.getDirectorySize(f"{self.user_data_path}{file_list[i]}")))
|
||||
orphans_row.add_suffix(select_orphans_tickbox)
|
||||
orphans_row.set_activatable_widget(select_orphans_tickbox)
|
||||
select_orphans_tickbox.connect("toggled", selection_handler, orphans_row.get_title())
|
||||
if is_select_all == True:
|
||||
select_orphans_tickbox.set_active(True)
|
||||
orphans_list.append(orphans_row)
|
||||
if not orphans_list.get_row_at_index(0):
|
||||
orphans_stack.set_visible_child(no_data)
|
||||
orphans_action_bar.set_revealed(False)
|
||||
|
||||
def key_handler(_a, event, _c, _d):
|
||||
if event == Gdk.KEY_Escape:
|
||||
orphans_window.close()
|
||||
elif event == Gdk.KEY_Delete or event == Gdk.KEY_BackSpace:
|
||||
trash_button_handler(event)
|
||||
|
||||
def trash_button_handler(widget):
|
||||
if total_selected == 0:
|
||||
return 1
|
||||
show_success = True
|
||||
for i in range(len(selected_rows)):
|
||||
path = f"{self.user_data_path}{selected_rows[i]}"
|
||||
try:
|
||||
subprocess.run(["flatpak-spawn", "--host", "gio", "trash", path], capture_output=False, check=True)
|
||||
except:
|
||||
orphans_toast_overlay.add_toast(Adw.Toast.new(_("Can't trash {}").format(selected_rows[i])))
|
||||
show_success = False
|
||||
select_all_button.set_active(False)
|
||||
|
||||
if show_success:
|
||||
orphans_toast_overlay.add_toast(Adw.Toast.new(_("Trashed data")))
|
||||
|
||||
generate_list(widget, False)
|
||||
|
||||
handler_id = 0
|
||||
|
||||
def install_callback(*_args):
|
||||
nonlocal should_pulse
|
||||
nonlocal handler_id
|
||||
|
||||
if self.install_success:
|
||||
orphans_toast_overlay.add_toast(Adw.Toast.new(_("Installed all apps")))
|
||||
else:
|
||||
orphans_toast_overlay.add_toast(Adw.Toast.new(_("Some apps didn't install")))
|
||||
select_all_button.set_active(False)
|
||||
orphans_progress_bar.set_visible(False)
|
||||
should_pulse = False
|
||||
self.refresh_list_of_flatpaks(None, False)
|
||||
generate_list(None, False)
|
||||
nonlocal orphans_toolbar_view
|
||||
orphans_toolbar_view.set_sensitive(True)
|
||||
orphans_window.disconnect(handler_id) # Make window able to close
|
||||
|
||||
def thread_func(id_list, remote):
|
||||
for i in range(len(id_list)):
|
||||
try:
|
||||
subprocess.run(["flatpak-spawn", "--host", "flatpak", "install", "-y", remote[0], f"--{remote[1]}", id_list[i]], capture_output=False, check=True)
|
||||
except subprocess.CalledProcessError:
|
||||
if remote[1] == "user":
|
||||
self.install_success = False
|
||||
continue
|
||||
try:
|
||||
subprocess.run(["flatpak-spawn", "--host", "pkexec", "flatpak", "install", "-y", remote[0], f"--{remote[1]}", id_list[i]], capture_output=False, check=True)
|
||||
except subprocess.CalledProcessError:
|
||||
self.install_success = False
|
||||
|
||||
def install_on_response(_a, response_id, _b):
|
||||
nonlocal should_pulse
|
||||
if response_id == "cancel":
|
||||
should_pulse = False
|
||||
orphans_progress_bar.set_visible(False)
|
||||
return 1
|
||||
|
||||
orphans_toast_overlay.add_toast(Adw.Toast.new(_("This could take some time")))
|
||||
nonlocal orphans_toolbar_view
|
||||
orphans_toolbar_view.set_sensitive(False)
|
||||
nonlocal handler_id
|
||||
handler_id = orphans_window.connect("close-request", lambda event: True) # Make window unable to close
|
||||
remote = response_id.split("_")
|
||||
|
||||
orphans_progress_bar.set_visible(True)
|
||||
|
||||
task = Gio.Task.new(None, None, install_callback)
|
||||
task.run_in_thread(lambda _task, _obj, _data, _cancellable, id_list=selected_rows, remote=remote: thread_func(id_list, remote))
|
||||
|
||||
def install_button_handler(widget):
|
||||
self.install_success = True
|
||||
nonlocal should_pulse
|
||||
should_pulse = True
|
||||
orphans_pulser()
|
||||
|
||||
def get_host_remotes():
|
||||
output = subprocess.run(["flatpak-spawn", "--host", "flatpak", "remotes"], 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)
|
||||
return data
|
||||
|
||||
host_remotes = get_host_remotes()
|
||||
|
||||
dialog = Adw.MessageDialog.new(self, _("Choose a Remote"))
|
||||
dialog.set_close_response("cancel")
|
||||
dialog.add_response("cancel", _("Cancel"))
|
||||
dialog.connect("response", install_on_response, dialog.choose_finish)
|
||||
dialog.set_transient_for(orphans_window)
|
||||
if len(host_remotes) > 1:
|
||||
dialog.set_body(_("Choose the Flatpak Remote Repository where attempted app downloads will be from."))
|
||||
for i in range(len(host_remotes)):
|
||||
remote_name = host_remotes[i][0]
|
||||
remote_option = host_remotes[i][1]
|
||||
dialog.add_response(f"{remote_name}_{remote_option}", f"{remote_name} {remote_option}")
|
||||
dialog.set_response_appearance(f"{remote_name}_{remote_option}", Adw.ResponseAppearance.SUGGESTED)
|
||||
else:
|
||||
remote_name = host_remotes[0][0]
|
||||
remote_option = host_remotes[0][1]
|
||||
dialog.set_heading("Attempt to Install Matching Flatpaks?")
|
||||
dialog.add_response(f"{remote_name}_{remote_option}", _("Continue"))
|
||||
Gtk.Window.present(dialog)
|
||||
|
||||
event_controller = Gtk.EventControllerKey()
|
||||
event_controller.connect("key-pressed", key_handler)
|
||||
orphans_window.add_controller(event_controller)
|
||||
|
||||
select_all_button = Gtk.ToggleButton(label=_("Select All"))
|
||||
select_all_button.connect("toggled", toggle_button_handler)
|
||||
orphans_action_bar.pack_start(select_all_button)
|
||||
|
||||
trash_button = Gtk.Button(label="Trash", valign=Gtk.Align.CENTER, tooltip_text=_("Trash Selected"))
|
||||
trash_button.add_css_class("destructive-action")
|
||||
trash_button.connect("clicked", trash_button_handler)
|
||||
orphans_action_bar.pack_end(trash_button)
|
||||
|
||||
install_button = Gtk.Button(label="Install", valign=Gtk.Align.CENTER, tooltip_text=_("Attempt to Install Selected"))
|
||||
install_button.connect("clicked", install_button_handler)
|
||||
install_button.set_visible(False)
|
||||
orphans_action_bar.pack_end(install_button)
|
||||
test = subprocess.run(["flatpak-spawn", "--host", "flatpak", "remotes"], capture_output=True, text=True).stdout
|
||||
for char in test:
|
||||
if char.isalnum():
|
||||
install_button.set_visible(True)
|
||||
|
||||
def selection_handler(tickbox, file):
|
||||
global total_selected
|
||||
global selected_rows
|
||||
if tickbox.get_active() == True:
|
||||
total_selected += 1
|
||||
selected_rows.append(file)
|
||||
else:
|
||||
total_selected -= 1
|
||||
to_find = file
|
||||
selected_rows.remove(to_find)
|
||||
|
||||
if total_selected == 0:
|
||||
orphans_window.set_title(window_title)
|
||||
trash_button.set_sensitive(False)
|
||||
install_button.set_sensitive(False)
|
||||
select_all_button.set_active(False)
|
||||
else:
|
||||
orphans_window.set_title(_("{} Selected").format(total_selected))
|
||||
trash_button.set_sensitive(True)
|
||||
install_button.set_sensitive(True)
|
||||
|
||||
generate_list(self, False)
|
||||
orphans_window.present()
|
||||
|
||||
selected_host_flatpak_indexes = []
|
||||
|
||||
def generate_list_of_flatpaks(self):
|
||||
|
||||
Reference in New Issue
Block a user