Files
warehouse/src/remotes_page/remotes_page.py
2025-01-05 22:20:05 -05:00

300 lines
9.8 KiB
Python

from gi.repository import Adw, Gtk, GLib, Gio
from src.host_info import HostInfo
from src.gtk.error_toast import ErrorToast
from src.remotes_page.remote_row import RemoteRow
from src.remotes_page.add_remote_dialog import AddRemoteDialog
from src.gtk.loading_status import LoadingStatus
import subprocess
import gettext
_ = gettext.gettext
class NewRemoteRow(Adw.ActionRow):
__gtype_name__ = "NewRemoteRow"
def idle_stuff(self, *args):
self.set_title(self.info["title"])
self.set_subtitle(self.info["description"])
self.add_suffix(Gtk.Image.new_from_icon_name("warehouse:plus-large-symbolic"))
def __init__(self, info, **kwargs):
super().__init__(**kwargs)
self.info = info
GLib.idle_add(self.idle_stuff)
self.set_activatable(True)
@Gtk.Template(resource_path="/io/github/flattool/Warehouse/remotes_page/remotes_page.ui")
class RemotesPage(Adw.NavigationPage):
# Preselected Remotes
new_remotes = [
{
"title": "AppCenter",
"name": "appcenter",
"link": "https://flatpak.elementary.io/repo.flatpakrepo",
"description": _("The open source, pay-what-you-want app store from elementary"),
},
{
"title": "Flathub",
"name": "flathub",
"link": "https://dl.flathub.org/repo/flathub.flatpakrepo",
"description": _("Central repository of Flatpak applications"),
},
{
"title": "Flathub beta",
"name": "flathub-beta",
"link": "https://flathub.org/beta-repo/flathub-beta.flatpakrepo",
"description": _("Beta builds of Flatpak applications"),
},
{
"title": "Fedora",
"name": "fedora",
"link": "oci+https://registry.fedoraproject.org",
"description": _("Flatpaks packaged by Fedora Linux"),
},
{
"title": "GNOME Nightly",
"name": "gnome-nightly",
"link": "https://nightly.gnome.org/gnome-nightly.flatpakrepo",
"description": _("The latest beta GNOME Apps and Runtimes"),
},
{
"title": "WebKit Developer SDK",
"name": "webkit-sdk",
"link": "https://software.igalia.com/flatpak-refs/webkit-sdk.flatpakrepo",
"description": _("Central repository of the WebKit Developer and Runtime SDK"),
},
]
__gtype_name__ = "RemotesPage"
gtc = Gtk.Template.Child
search_button = gtc()
search_bar = gtc()
search_entry = gtc()
toast_overlay = gtc()
stack = gtc()
current_remotes_group = gtc()
show_disabled_button = gtc()
show_disabled_button_content = gtc()
new_remotes_group = gtc()
file_remote_row = gtc()
custom_remote_row = gtc()
none_visible = gtc()
status_stack = gtc()
loading_view = gtc()
adding_view = gtc()
main_view = gtc()
no_results = gtc()
no_remotes = gtc()
content_page = gtc()
# Referred to in the main window
# It is used to determine if a new page should be made or not
# This must be set to the created object from within the class's __init__ method
instance = None
page_name = "remotes"
def start_loading(self):
self.search_button.set_active(False)
self.status_stack.set_visible_child(self.loading_view)
self.total_disabled = 0
for row in self.current_remote_rows:
self.current_remotes_group.remove(row)
self.current_remote_rows.clear()
def end_loading(self):
show_disabled = self.show_disabled_button.get_active()
self.show_disabled_button.set_visible(False)
total_visible = 0
for installation, remotes in HostInfo.remotes.items():
for remote in remotes:
row = RemoteRow(self, installation, remote)
self.current_remote_rows.append(row)
self.current_remotes_group.add(row)
if row.remote.disabled:
self.total_disabled += 1
self.show_disabled_button.set_visible(True)
if show_disabled:
total_visible += 1
else:
row.set_visible(False)
else:
total_visible += 1
self.none_visible.set_visible(total_visible == 0)
if len(self.current_remote_rows) == 0:
self.no_remotes.set_visible(True)
self.none_visible.set_visible(False)
else:
self.no_remotes.set_visible(False)
GLib.idle_add(lambda *_: self.status_stack.set_visible_child(self.main_view))
def none_visible_handler(self):
any_visible = False
for row in self.current_remote_rows:
if row.get_visible():
any_visible = True
break
self.none_visible.set_visible(not any_visible)
def filter_remote(self, row):
self.filter_setting.set_boolean("show-apps", True)
self.filter_setting.set_boolean("show-runtimes", True)
self.filter_setting.set_string("remotes-list", f"{row.remote.name}<>{row.installation};")
self.filter_setting.reset("runtimes-list")
packages_page = self.main_window.pages[self.main_window.packages_row]
packages_page.filters_page.generate_filters()
packages_page.apply_filters()
GLib.idle_add(lambda *_: self.main_window.activate_row(self.main_window.packages_row))
GLib.idle_add(lambda *args: packages_page.packages_toast_overlay.add_toast(Adw.Toast(title=_("Showing all packages from {}").format(row.remote.title))))
def remove_remote(self, row):
error = [None]
def thread(*args):
install = row.installation
cmd = ["flatpak-spawn", "--host", "flatpak", "remote-delete", row.remote.name, "--force"]
if install == "user" or install == "system":
cmd.append(f"--{install}")
else:
cmd.append(f"--installation={install}")
try:
subprocess.run(cmd, check=True, capture_output=True, text=True)
except subprocess.CalledProcessError as cpe:
error[0] = cpe.stderr
except Exception as e:
error[0] = e
def callback(*args):
if error[0]:
self.toast_overlay.add_toast(ErrorToast(_("Could not remove remote"), str(error[0])).toast)
else:
filters_page = HostInfo.main_window.pages[HostInfo.main_window.packages_row].filters_page
filters_page.settings.reset("remotes-list")
filters_page.all_remotes_switch.set_active(False)
# filters_page.packages_page.apply_filters()
self.main_window.refresh_handler()
self.toast_overlay.add_toast(Adw.Toast(title=_("Removed {}").format(row.remote.title)))
def on_response(_, response):
if response != "continue":
return
Gio.Task.new(None, None, callback).run_in_thread(thread)
dialog = Adw.AlertDialog(
heading=_("Remove {}?").format(row.remote.title), body=_("Any installed apps from {} will stop receiving updates").format(row.remote.name)
)
dialog.add_response("cancel", _("Cancel"))
dialog.add_response("continue", _("Remove"))
dialog.set_response_appearance("continue", Adw.ResponseAppearance.DESTRUCTIVE)
dialog.connect("response", on_response)
dialog.present(self.main_window)
def on_search(self, entry):
text = entry.get_text().lower()
total = 0
show_disabled = self.show_disabled_button.get_active()
for row in self.current_remote_rows:
title_match = text in row.get_title().lower()
subtitle_match = text in row.get_subtitle().lower()
visible = (title_match or subtitle_match) and (show_disabled or not row.remote.disabled)
total += visible
row.set_visible(visible)
if text == "":
self.stack.set_visible_child(self.content_page)
return
self.stack.set_visible_child(self.content_page if total > 0 else self.no_results)
def local_file_handler(self, path):
try:
name = path.split("/")[-1].split(".")[0]
info = {
"title": name.title(),
"name": name,
"description": "local file",
"link": path,
}
AddRemoteDialog(self.main_window, self, info).present(self.main_window)
except Exception as e:
self.toast_overlay.add_toast(ErrorToast(_("Could not open file"), str(e)).toast)
def file_callback(self, chooser, result):
try:
file = chooser.open_finish(result)
path = file.get_path()
self.local_file_handler(path)
except GLib.GError as ge:
if "Dismissed by user" in str(ge):
return
self.toast_overlay.add_toast(ErrorToast(_("Could not open file"), str(ge)).toast)
except Exception as e:
self.toast_overlay.add_toast(ErrorToast(_("Could not open file"), str(e)).toast)
def add_file_handler(self):
file_filter = Gtk.FileFilter(name=_("Flatpak Repos"))
file_filter.add_suffix("flatpakrepo")
filters = Gio.ListStore.new(Gtk.FileFilter)
filters.append(file_filter)
file_chooser = Gtk.FileDialog()
file_chooser.set_filters(filters)
file_chooser.set_default_filter(file_filter)
file_chooser.open(self.main_window, None, self.file_callback)
def show_disabled_handler(self, button):
show_disabled = button.get_active()
self.show_disabled_button_content.set_icon_name(
"warehouse:eye-open-negative-filled-symbolic" if show_disabled else "warehouse:eye-not-looking-symbolic"
)
total_visible = 0
for row in self.current_remote_rows:
if row.remote.disabled:
if show_disabled: # show disabled
row.set_visible(True)
total_visible += 1
else:
row.set_visible(False)
else:
total_visible += 1
self.none_visible.set_visible(total_visible == 0)
def new_custom_handler(self, *args):
AddRemoteDialog(self.main_window, self).present(self.main_window)
def __init__(self, main_window, **kwargs):
super().__init__(**kwargs)
# Extra Object Creation
self.__class__.instance = self
self.main_window = main_window
self.search_bar.set_key_capture_widget(main_window)
self.current_remote_rows = []
self.filter_setting = Gio.Settings.new("io.github.flattool.Warehouse.filter")
self.total_disabled = 0
# Connections
self.file_remote_row.connect("activated", lambda *_: self.add_file_handler())
self.custom_remote_row.connect("activated", self.new_custom_handler)
self.search_entry.connect("search-changed", self.on_search)
self.show_disabled_button.connect("toggled", self.show_disabled_handler)
# Apply
self.adding_view.set_content(LoadingStatus(_("Adding Remote"), _("This should only take a moment")))
self.loading_view.set_content(LoadingStatus(_("Loading Remotes"), _("This should only take a moment")))
for item in self.new_remotes:
row = NewRemoteRow(item)
row.connect("activated", lambda *_, remote_info=item: AddRemoteDialog(main_window, self, remote_info).present(main_window))
self.new_remotes_group.add(row)