diff --git a/po/POTFILES.in b/po/POTFILES.in
index eeffd344..dc217582 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -15,17 +15,18 @@ src/bz-app-size-dialog.blp
src/bz-app-size-dialog.c
src/bz-app-tile.blp
src/bz-app-tile.c
-src/bz-apps-page.blp
-src/bz-apps-page.c
src/bz-application-map-factory.c
src/bz-application.c
+src/bz-apps-page.blp
+src/bz-apps-page.c
src/bz-appstream-parser.c
src/bz-async-texture.c
src/bz-backend.c
+src/bz-bundle-install-dialog.c
src/bz-content-provider.c
+src/bz-context-tile-callbacks.c
src/bz-context-tile.blp
src/bz-context-tile.c
-src/bz-context-tile-callbacks.c
src/bz-curated-view.blp
src/bz-curated-view.c
src/bz-data-graph.c
@@ -38,23 +39,23 @@ src/bz-donations-dialog.c
src/bz-download-worker.c
src/bz-dynamic-list-view.c
src/bz-entry-cache-manager.c
-src/bz-entry-group.c
src/bz-entry-group-util.c
+src/bz-entry-group.c
src/bz-entry-inspector.blp
src/bz-entry-inspector.c
src/bz-entry-selection-row.blp
src/bz-entry-selection-row.c
src/bz-entry.c
src/bz-env.c
-src/bz-error.c
src/bz-error-dialog.blp
src/bz-error-dialog.c
-src/bz-favorite-button.c
+src/bz-error.c
src/bz-favorite-button.blp
-src/bz-favorites-tile.c
-src/bz-favorites-tile.blp
-src/bz-favorites-page.c
+src/bz-favorite-button.c
src/bz-favorites-page.blp
+src/bz-favorites-page.c
+src/bz-favorites-tile.blp
+src/bz-favorites-tile.c
src/bz-featured-carousel.blp
src/bz-featured-tile.blp
src/bz-flathub-category-section.c
@@ -101,15 +102,15 @@ src/bz-rich-app-tile.c
src/bz-safety-calculator.c
src/bz-safety-dialog.blp
src/bz-safety-dialog.c
-src/bz-screenshot.c
src/bz-screenshot-page.blp
+src/bz-screenshot.c
src/bz-screenshots-carousel.blp
src/bz-search-engine.c
-src/bz-search-pill-list.c
src/bz-search-filter-popover.blp
src/bz-search-filter-popover.c
src/bz-search-page.blp
src/bz-search-page.c
+src/bz-search-pill-list.c
src/bz-section-view.blp
src/bz-section-view.c
src/bz-serializable.c
@@ -126,14 +127,14 @@ src/bz-transaction-tile.c
src/bz-transaction.c
src/bz-updates-card.blp
src/bz-updates-card.c
-src/bz-user-data-page.c
src/bz-user-data-page.blp
-src/bz-user-data-tile.c
+src/bz-user-data-page.c
src/bz-user-data-tile.blp
+src/bz-user-data-tile.c
src/bz-window.blp
src/bz-window.c
src/bz-world-map.c
src/bz-yaml-parser.c
src/dl-worker.c
-src/shortcuts-dialog.blp
src/main.c
+src/shortcuts-dialog.blp
diff --git a/src/bazaar.gresource.xml b/src/bazaar.gresource.xml
index ef4d6466..240f6629 100644
--- a/src/bazaar.gresource.xml
+++ b/src/bazaar.gresource.xml
@@ -18,19 +18,20 @@
bz-addon-tile.ui
bz-addons-dialog.ui
- bz-donations-dialog.ui
bz-age-rating-dialog.ui
+ bz-all-apps-page.ui
bz-app-size-dialog.ui
bz-app-tile.ui
- bz-all-apps-page.ui
bz-apps-page.ui
bz-appstream-description-render.ui
- bz-curated-view.ui
+ bz-bundle-install-dialog.ui
bz-category-tile.ui
bz-context-tile.ui
bz-curated-app-tile.ui
+ bz-curated-view.ui
bz-decorated-screenshot.ui
bz-developer-badge.ui
+ bz-donations-dialog.ui
bz-entry-inspector.ui
bz-entry-selection-row.ui
bz-error-dialog.ui
@@ -45,8 +46,8 @@
bz-hardware-support-dialog.ui
bz-inspector.ui
bz-install-controls.ui
- bz-library-page.ui
bz-installed-tile.ui
+ bz-library-page.ui
bz-license-dialog.ui
bz-login-page.ui
bz-preferences-dialog.ui
@@ -58,12 +59,12 @@
bz-safety-dialog.ui
bz-screenshot-page.ui
bz-screenshots-carousel.ui
- bz-search-page.ui
bz-search-filter-popover.ui
+ bz-search-page.ui
bz-section-view.ui
bz-stats-dialog.ui
- bz-transaction-tile.ui
bz-transaction-list-dialog.ui
+ bz-transaction-tile.ui
bz-updates-card.ui
bz-user-data-page.ui
bz-user-data-tile.ui
diff --git a/src/bz-application.c b/src/bz-application.c
index 61c1b659..1647393d 100644
--- a/src/bz-application.c
+++ b/src/bz-application.c
@@ -33,6 +33,7 @@
#include "bz-appstream-parser.h"
#include "bz-auth-state.h"
#include "bz-backend-notification.h"
+#include "bz-bundle-install-dialog.h"
#include "bz-content-provider.h"
#include "bz-donations-dialog.h"
#include "bz-download-worker.h"
@@ -1246,7 +1247,17 @@ enumerate_disk_entries_fiber (GWeakRef *wr)
future = g_ptr_array_index (futures, i);
value = dex_future_get_value (future, &local_error);
if (value != NULL)
- g_ptr_array_add (entries, g_value_dup_object (value));
+ {
+ g_autoptr (BzEntry) entry = NULL;
+
+ entry = g_value_dup_object (value);
+ if (BZ_IS_FLATPAK_ENTRY (entry) &&
+ bz_flatpak_entry_get_bundle_path (BZ_FLATPAK_ENTRY (entry)) != NULL)
+ /* refrain from restoring bundle entries */
+ continue;
+
+ g_ptr_array_add (entries, g_steal_pointer (&entry));
+ }
else
{
g_warning ("Unable to retrieve cached entry: %s", local_error->message);
@@ -1363,6 +1374,17 @@ respond_to_flatpak_fiber (RespondToFlatpakData *data)
kind = bz_backend_notification_get_kind (notif);
switch (kind)
{
+ case BZ_BACKEND_NOTIFICATION_KIND_PRESENT_ID:
+ {
+ const char *id = NULL;
+
+ id = bz_backend_notification_get_generic_id (notif);
+ if (id == NULL)
+ break;
+
+ open_generic_id (self, id);
+ }
+ break;
case BZ_BACKEND_NOTIFICATION_KIND_ERROR:
{
const char *error = NULL;
@@ -1542,11 +1564,13 @@ respond_to_flatpak_fiber (RespondToFlatpakData *data)
}
break;
case BZ_BACKEND_NOTIFICATION_KIND_ERROR:
- case BZ_BACKEND_NOTIFICATION_KIND_TELL_INCOMING:
- case BZ_BACKEND_NOTIFICATION_KIND_REPLACE_ENTRY:
- case BZ_BACKEND_NOTIFICATION_KIND_REMOTE_SYNC_START:
- case BZ_BACKEND_NOTIFICATION_KIND_REMOTE_SYNC_FINISH:
case BZ_BACKEND_NOTIFICATION_KIND_EXTERNAL_CHANGE:
+ case BZ_BACKEND_NOTIFICATION_KIND_INVALIDATE_REMOTES:
+ case BZ_BACKEND_NOTIFICATION_KIND_PRESENT_ID:
+ case BZ_BACKEND_NOTIFICATION_KIND_REMOTE_SYNC_FINISH:
+ case BZ_BACKEND_NOTIFICATION_KIND_REMOTE_SYNC_START:
+ case BZ_BACKEND_NOTIFICATION_KIND_REPLACE_ENTRY:
+ case BZ_BACKEND_NOTIFICATION_KIND_TELL_INCOMING:
default:
g_assert_not_reached ();
};
@@ -1564,8 +1588,10 @@ respond_to_flatpak_fiber (RespondToFlatpakData *data)
}
}
break;
+ case BZ_BACKEND_NOTIFICATION_KIND_INVALIDATE_REMOTES:
case BZ_BACKEND_NOTIFICATION_KIND_EXTERNAL_CHANGE:
{
+ g_autoptr (GListModel) repos = NULL;
g_autoptr (GHashTable) installed_set = NULL;
g_autoptr (GPtrArray) diff_reads = NULL;
GHashTableIter old_iter = { 0 };
@@ -1574,6 +1600,18 @@ respond_to_flatpak_fiber (RespondToFlatpakData *data)
bz_state_info_set_background_task_label (self->state, _ ("Refreshing…"));
+ repos = dex_await_object (
+ bz_backend_list_repositories (BZ_BACKEND (self->flatpak), NULL),
+ &local_error);
+
+ if (repos != NULL)
+ bz_state_info_set_repositories (self->state, repos);
+ else
+ {
+ g_warning ("Failed to enumerate repositories: %s", local_error->message);
+ g_clear_error (&local_error);
+ }
+
installed_set = dex_await_boxed (
bz_backend_retrieve_install_ids (
BZ_BACKEND (self->flatpak), NULL),
@@ -1763,7 +1801,6 @@ open_flatpakref_fiber (OpenFlatpakrefData *data)
GFile *file = data->file;
g_autoptr (GError) local_error = NULL;
g_autoptr (DexFuture) future = NULL;
- GtkWindow *window = NULL;
const GValue *value = NULL;
bz_weak_get_or_return_reject (self, data->self);
@@ -1772,25 +1809,47 @@ open_flatpakref_fiber (OpenFlatpakrefData *data)
future = bz_backend_load_local_package (BZ_BACKEND (self->flatpak), file, NULL);
dex_await (dex_ref (future), NULL);
- window = gtk_application_get_active_window (GTK_APPLICATION (self));
- if (window == NULL)
- window = new_window (self);
-
value = dex_future_get_value (future, &local_error);
- if (value != NULL)
+ if (value == NULL)
{
- if (G_VALUE_HOLDS_OBJECT (value))
- {
- BzEntry *entry = NULL;
+ GtkWindow *window = NULL;
- entry = g_value_get_object (value);
- bz_window_show_entry (BZ_WINDOW (window), entry);
- }
- else
- open_generic_id (self, g_value_get_string (value));
+ window = gtk_application_get_active_window (GTK_APPLICATION (self));
+ if (window == NULL)
+ window = new_window (self);
+
+ bz_show_error_for_widget (
+ GTK_WIDGET (window),
+ _ ("Failed to open file"),
+ local_error->message);
+
+ return dex_future_new_for_error (g_steal_pointer (&local_error));
+ }
+
+ if (G_VALUE_HOLDS_OBJECT (value))
+ {
+ GtkWindow *window = NULL;
+ BzEntry *entry = NULL;
+ BzBundleInstallDialog *install_ui = NULL;
+ AdwDialog *dialog = NULL;
+
+ window = gtk_application_get_active_window (GTK_APPLICATION (self));
+ if (window == NULL)
+ window = new_window (self);
+
+ entry = g_value_get_object (value);
+ install_ui = g_object_new (
+ BZ_TYPE_BUNDLE_INSTALL_DIALOG,
+ "state", self->state,
+ "entry", entry,
+ NULL);
+
+ dialog = adw_dialog_new ();
+ adw_dialog_set_follows_content_size (dialog, TRUE);
+ adw_dialog_set_child (dialog, GTK_WIDGET (install_ui));
+
+ adw_dialog_present (dialog, GTK_WIDGET (window));
}
- else
- bz_show_error_for_widget (GTK_WIDGET (window), _ ("Failed to open .flatpakref"), local_error->message);
return dex_future_new_true ();
}
@@ -2003,9 +2062,7 @@ ensure_group_and_add (BzApplication *self,
group = g_hash_table_lookup (self->ids_to_groups, id);
if (group != NULL)
- {
- bz_entry_group_add (group, entry, eol_runtime, ignore_eol);
- }
+ bz_entry_group_add (group, entry, eol_runtime, ignore_eol);
else
{
g_autoptr (BzEntryGroup) new_group = NULL;
@@ -3201,11 +3258,46 @@ static void
open_flatpakref_take (BzApplication *self,
GFile *file)
{
+ g_autoptr (GError) local_error = NULL;
+ gboolean result = FALSE;
g_autofree char *path = NULL;
g_autoptr (OpenFlatpakrefData) data = NULL;
path = g_file_get_path (file);
- g_info ("Loading flatpakref at %s...", path);
+ if (path != NULL)
+ /* We must do this synchronously so we don't lose access to a portal file */
+ {
+ g_autofree char *basename = NULL;
+ g_autofree char *module_dir = NULL;
+ g_autofree char *staging = NULL;
+ g_autofree char *dest = NULL;
+ g_autoptr (GFile) copied = NULL;
+
+ basename = g_file_get_basename (file);
+ module_dir = bz_dup_module_dir ();
+ staging = g_build_filename (module_dir, "bundle-staging", NULL);
+ g_mkdir_with_parents (staging, 0755);
+ dest = g_build_filename (staging, basename, NULL);
+ copied = g_file_new_for_path (dest);
+
+ result = g_file_copy (
+ file, copied,
+ G_FILE_COPY_OVERWRITE | G_FILE_COPY_NOFOLLOW_SYMLINKS,
+ NULL, NULL, NULL,
+ &local_error);
+ if (result)
+ {
+ g_clear_object (&file);
+ file = g_steal_pointer (&copied);
+ }
+ else
+ {
+ g_warning ("Failed to copy bundle to %s : %s",
+ dest, local_error->message);
+ g_clear_error (&local_error);
+ g_clear_object (&copied);
+ }
+ }
data = open_flatpakref_data_new ();
data->self = bz_track_weak (self);
diff --git a/src/bz-backend-notification.txt b/src/bz-backend-notification.txt
index 3816118f..50b32a77 100644
--- a/src/bz-backend-notification.txt
+++ b/src/bz-backend-notification.txt
@@ -4,7 +4,7 @@ parent-prefix=g
parent-name=object
author=AUTOGEN
-enum=bz backend_notification_kind error tell_incoming replace_entry remote_sync_start remote_sync_finish install_done update_done remove_done external_change
+enum=bz backend_notification_kind error tell_incoming replace_entry invalidate_remotes remote_sync_start remote_sync_finish install_done update_done remove_done external_change present_id
include="bz-entry.h"
@@ -14,4 +14,5 @@ property=n_incoming int G_TYPE_INT int
property=entry BzEntry BZ_TYPE_ENTRY object
property=version char G_TYPE_STRING string
property=remote_name char G_TYPE_STRING string
+property=generic_id char G_TYPE_STRING string
property=unique_id char G_TYPE_STRING string
diff --git a/src/bz-bundle-install-dialog.blp b/src/bz-bundle-install-dialog.blp
new file mode 100644
index 00000000..e8ac79c7
--- /dev/null
+++ b/src/bz-bundle-install-dialog.blp
@@ -0,0 +1,51 @@
+using Gtk 4.0;
+using Adw 1;
+
+template $BzBundleInstallDialog: Adw.BreakpointBin {
+ width-request: 360;
+ height-request: 450;
+ child: Adw.ToolbarView {
+ bottom-bar-style: raised_border;
+
+ [top]
+ Adw.HeaderBar {
+ title-widget: Label {
+ label: _("Bundle Installation");
+ };
+ }
+
+ content: Box {
+ orientation: vertical;
+ spacing: 5;
+
+ Image {
+ paintable: bind template.entry as <$BzEntry>.icon-paintable;
+ pixel-size: 128;
+ }
+ Label {
+ label: bind template.entry as <$BzEntry>.title;
+ wrap: true;
+ }
+ Label {
+ label: bind template.entry as <$BzEntry>.developer;
+ wrap: true;
+ }
+ Label {
+ label: bind template.entry as <$BzEntry>.description;
+ wrap: true;
+ }
+
+ Button install_btn {
+ styles [
+ "suggested-action",
+ ]
+ halign: center;
+ label: _("Install Bundle");
+ clicked => $install_cb(template);
+ }
+
+ Label status_lbl {
+ }
+ };
+ };
+}
diff --git a/src/bz-bundle-install-dialog.c b/src/bz-bundle-install-dialog.c
new file mode 100644
index 00000000..18df1d35
--- /dev/null
+++ b/src/bz-bundle-install-dialog.c
@@ -0,0 +1,260 @@
+/* bz-bundle-install-dialog.c
+ *
+ * Copyright 2026 Eva M
+ *
+ * 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, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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-or-later
+ */
+
+#include
+
+#include "bz-bundle-install-dialog.h"
+#include "bz-env.h"
+#include "bz-template-callbacks.h"
+#include "bz-util.h"
+
+struct _BzBundleInstallDialog
+{
+ AdwBreakpointBin parent_instance;
+
+ BzStateInfo *state;
+ BzEntry *entry;
+
+ GtkButton *install_btn;
+ GtkLabel *status_lbl;
+};
+
+G_DEFINE_FINAL_TYPE (BzBundleInstallDialog, bz_bundle_install_dialog, ADW_TYPE_BREAKPOINT_BIN);
+
+enum
+{
+ PROP_0,
+
+ PROP_STATE,
+ PROP_ENTRY,
+
+ LAST_PROP
+};
+static GParamSpec *props[LAST_PROP] = { 0 };
+
+static DexFuture *
+install_fiber (GWeakRef *wr);
+
+static void
+bz_bundle_install_dialog_dispose (GObject *object)
+{
+ BzBundleInstallDialog *self = BZ_BUNDLE_INSTALL_DIALOG (object);
+
+ g_clear_pointer (&self->state, g_object_unref);
+ g_clear_pointer (&self->entry, g_object_unref);
+
+ G_OBJECT_CLASS (bz_bundle_install_dialog_parent_class)->dispose (object);
+}
+
+static void
+bz_bundle_install_dialog_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ BzBundleInstallDialog *self = BZ_BUNDLE_INSTALL_DIALOG (object);
+
+ switch (prop_id)
+ {
+ case PROP_STATE:
+ g_value_set_object (value, bz_bundle_install_dialog_get_state (self));
+ break;
+ case PROP_ENTRY:
+ g_value_set_object (value, bz_bundle_install_dialog_get_entry (self));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+bz_bundle_install_dialog_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ BzBundleInstallDialog *self = BZ_BUNDLE_INSTALL_DIALOG (object);
+
+ switch (prop_id)
+ {
+ case PROP_STATE:
+ bz_bundle_install_dialog_set_state (self, g_value_get_object (value));
+ break;
+ case PROP_ENTRY:
+ bz_bundle_install_dialog_set_entry (self, g_value_get_object (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+install_cb (BzBundleInstallDialog *self,
+ GtkButton *button)
+{
+ if (self->entry == NULL ||
+ self->state == NULL)
+ return;
+
+ gtk_widget_set_sensitive (GTK_WIDGET (self->install_btn), FALSE);
+ dex_future_disown (dex_scheduler_spawn (
+ dex_scheduler_get_default (),
+ bz_get_dex_stack_size (),
+ (DexFiberFunc) install_fiber,
+ bz_track_weak (self),
+ bz_weak_release));
+}
+
+static void
+bz_bundle_install_dialog_class_init (BzBundleInstallDialogClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->set_property = bz_bundle_install_dialog_set_property;
+ object_class->get_property = bz_bundle_install_dialog_get_property;
+ object_class->dispose = bz_bundle_install_dialog_dispose;
+
+ props[PROP_STATE] =
+ g_param_spec_object (
+ "state",
+ NULL, NULL,
+ BZ_TYPE_STATE_INFO,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ props[PROP_ENTRY] =
+ g_param_spec_object (
+ "entry",
+ NULL, NULL,
+ BZ_TYPE_ENTRY,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ g_object_class_install_properties (object_class, LAST_PROP, props);
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/io/github/kolunmi/Bazaar/bz-bundle-install-dialog.ui");
+ bz_widget_class_bind_all_util_callbacks (widget_class);
+
+ gtk_widget_class_bind_template_child (widget_class, BzBundleInstallDialog, install_btn);
+ gtk_widget_class_bind_template_child (widget_class, BzBundleInstallDialog, status_lbl);
+ gtk_widget_class_bind_template_callback (widget_class, install_cb);
+}
+
+static void
+bz_bundle_install_dialog_init (BzBundleInstallDialog *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+BzBundleInstallDialog *
+bz_bundle_install_dialog_new (void)
+{
+ return g_object_new (BZ_TYPE_BUNDLE_INSTALL_DIALOG, NULL);
+}
+
+BzStateInfo *
+bz_bundle_install_dialog_get_state (BzBundleInstallDialog *self)
+{
+ g_return_val_if_fail (BZ_IS_BUNDLE_INSTALL_DIALOG (self), NULL);
+ return self->state;
+}
+
+BzEntry *
+bz_bundle_install_dialog_get_entry (BzBundleInstallDialog *self)
+{
+ g_return_val_if_fail (BZ_IS_BUNDLE_INSTALL_DIALOG (self), NULL);
+ return self->entry;
+}
+
+void
+bz_bundle_install_dialog_set_state (BzBundleInstallDialog *self,
+ BzStateInfo *state)
+{
+ g_return_if_fail (BZ_IS_BUNDLE_INSTALL_DIALOG (self));
+ g_return_if_fail (state == NULL || BZ_IS_STATE_INFO (state));
+
+ if (state == self->state)
+ return;
+
+ g_clear_pointer (&self->state, g_object_unref);
+ if (state != NULL)
+ self->state = g_object_ref (state);
+
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_STATE]);
+}
+
+void
+bz_bundle_install_dialog_set_entry (BzBundleInstallDialog *self,
+ BzEntry *entry)
+{
+ g_return_if_fail (BZ_IS_BUNDLE_INSTALL_DIALOG (self));
+ g_return_if_fail (entry == NULL || BZ_IS_ENTRY (entry));
+
+ if (entry == self->entry)
+ return;
+
+ g_clear_pointer (&self->entry, g_object_unref);
+ if (entry != NULL)
+ self->entry = g_object_ref (entry);
+
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ENTRY]);
+}
+
+static DexFuture *
+install_fiber (GWeakRef *wr)
+{
+ g_autoptr (BzBundleInstallDialog) self = NULL;
+ g_autoptr (GError) local_error = NULL;
+ g_autoptr (BzTransaction) transaction = NULL;
+ g_autoptr (BzTransactionManager) ts_manager = NULL;
+
+ bz_weak_get_or_return_reject (self, wr);
+
+ if (self->entry == NULL ||
+ self->state == NULL)
+ return dex_future_new_false ();
+
+ transaction = bz_transaction_new_full (
+ &self->entry, 1,
+ NULL, 0,
+ NULL, 0);
+
+ ts_manager = g_object_ref (bz_state_info_get_transaction_manager (self->state));
+
+ gtk_label_set_label (self->status_lbl, _ ("Installing"));
+ dex_await (
+ bz_transaction_manager_add (
+ ts_manager, transaction),
+ &local_error);
+
+ if (local_error != NULL)
+ {
+ gtk_widget_add_css_class (GTK_WIDGET (self->status_lbl), "error");
+ gtk_label_set_label (self->status_lbl, local_error->message);
+ }
+ else
+ {
+ gtk_widget_add_css_class (GTK_WIDGET (self->status_lbl), "success");
+ gtk_label_set_label (self->status_lbl, _ ("Installed!"));
+ }
+
+ return dex_future_new_true ();
+}
+
+/* End of bz-bundle-install-dialog.c */
diff --git a/src/bz-bundle-install-dialog.h b/src/bz-bundle-install-dialog.h
new file mode 100644
index 00000000..abb18984
--- /dev/null
+++ b/src/bz-bundle-install-dialog.h
@@ -0,0 +1,52 @@
+/* bz-bundle-install-dialog.h
+ *
+ * Copyright 2026 Eva M
+ *
+ * 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, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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-or-later
+ */
+
+#pragma once
+
+#include
+
+#include "bz-entry.h"
+#include "bz-state-info.h"
+
+G_BEGIN_DECLS
+
+#define BZ_TYPE_BUNDLE_INSTALL_DIALOG (bz_bundle_install_dialog_get_type ())
+G_DECLARE_FINAL_TYPE (BzBundleInstallDialog, bz_bundle_install_dialog, BZ, BUNDLE_INSTALL_DIALOG, AdwBreakpointBin)
+
+BzBundleInstallDialog *
+bz_bundle_install_dialog_new (void);
+
+BzStateInfo *
+bz_bundle_install_dialog_get_state (BzBundleInstallDialog *self);
+
+BzEntry *
+bz_bundle_install_dialog_get_entry (BzBundleInstallDialog *self);
+
+void
+bz_bundle_install_dialog_set_state (BzBundleInstallDialog *self,
+ BzStateInfo *state);
+
+void
+bz_bundle_install_dialog_set_entry (BzBundleInstallDialog *self,
+ BzEntry *entry);
+
+G_END_DECLS
+
+/* End of bz-bundle-install-dialog.h */
diff --git a/src/bz-entry-group.c b/src/bz-entry-group.c
index 1e939db6..8700c543 100644
--- a/src/bz-entry-group.c
+++ b/src/bz-entry-group.c
@@ -23,18 +23,29 @@
#include "bz-entry-group.h"
#include "bz-env.h"
-#include "bz-flatpak-entry.h"
#include "bz-io.h"
#include "bz-util.h"
+typedef enum
+{
+ ENTRY_INSTALLABLE = 1 << 0,
+ ENTRY_INSTALLABLE_AVAILABLE = 1 << 1,
+ ENTRY_UPDATABLE = 1 << 2,
+ ENTRY_UPDATABLE_AVAILABLE = 1 << 3,
+ ENTRY_REMOVABLE = 1 << 4,
+ ENTRY_REMOVABLE_AVAILABLE = 1 << 5,
+} EntryStateFlags;
+
struct _BzEntryGroup
{
GObject parent_instance;
BzApplicationMapFactory *factory;
- GtkStringList *unique_ids;
- GtkStringList *installed_versions;
+ GtkStringList *unique_ids;
+ GtkStringList *installed_versions;
+ GArray *state_flags;
+
char *id;
char *title;
char *developer;
@@ -142,8 +153,11 @@ bz_entry_group_dispose (GObject *object)
dex_clear (&self->user_data_size_future);
dex_clear (&self->reap_user_data_future);
g_clear_object (&self->factory);
+
g_clear_object (&self->unique_ids);
g_clear_object (&self->installed_versions);
+ g_clear_pointer (&self->state_flags, g_array_unref);
+
g_clear_pointer (&self->id, g_free);
g_clear_pointer (&self->title, g_free);
g_clear_pointer (&self->developer, g_free);
@@ -491,7 +505,9 @@ bz_entry_group_init (BzEntryGroup *self)
{
self->unique_ids = gtk_string_list_new (NULL);
self->installed_versions = gtk_string_list_new (NULL);
- self->max_usefulness = -1;
+ self->state_flags = g_array_new (FALSE, TRUE, sizeof (gint32));
+
+ self->max_usefulness = -1;
g_weak_ref_init (&self->ui_entry, NULL);
self->standalone_ui_entry = NULL;
g_mutex_init (&self->mutex);
@@ -910,6 +926,7 @@ bz_entry_group_add (BzEntryGroup *self,
gboolean is_searchable = FALSE;
AsContentRating *content_rating = NULL;
gboolean is_addon = FALSE;
+ gint32 state_flags = 0;
g_return_if_fail (BZ_IS_ENTRY_GROUP (self));
g_return_if_fail (BZ_IS_ENTRY (entry));
@@ -1131,39 +1148,60 @@ bz_entry_group_add (BzEntryGroup *self,
}
}
- if (existing == G_MAXUINT)
+ if (existing != G_MAXUINT)
{
- if (bz_entry_is_installed (entry))
+ gint32 previous_state_flags = 0;
+
+ /* revert the old state if we are replacing */
+
+ previous_state_flags = g_array_index (self->state_flags, gint32, existing);
+ if (previous_state_flags & ENTRY_INSTALLABLE)
+ self->installable--;
+ if (previous_state_flags & ENTRY_INSTALLABLE_AVAILABLE)
+ self->installable_available--;
+ if (previous_state_flags & ENTRY_UPDATABLE)
+ self->updatable--;
+ if (previous_state_flags & ENTRY_UPDATABLE_AVAILABLE)
+ self->updatable_available--;
+ if (previous_state_flags & ENTRY_REMOVABLE)
+ self->removable--;
+ if (previous_state_flags & ENTRY_REMOVABLE_AVAILABLE)
+ self->removable_available--;
+ }
+
+ if (bz_entry_is_installed (entry))
+ {
+ self->removable++;
+ state_flags |= ENTRY_REMOVABLE;
+ if (!bz_entry_is_holding (entry))
{
- self->removable++;
- if (!bz_entry_is_holding (entry))
- {
- self->removable_available++;
- g_object_notify_by_pspec (G_OBJECT (self), props[PROP_REMOVABLE_AND_AVAILABLE]);
- }
- g_object_notify_by_pspec (G_OBJECT (self), props[PROP_REMOVABLE]);
+ self->removable_available++;
+ state_flags |= ENTRY_REMOVABLE_AVAILABLE;
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_REMOVABLE_AND_AVAILABLE]);
}
- else
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_REMOVABLE]);
+ }
+ else
+ {
+ if (bz_entry_is_reinstallable (entry))
{
- gboolean is_installed_ref = FALSE;
-
- if (BZ_IS_FLATPAK_ENTRY (entry))
- is_installed_ref = bz_flatpak_entry_is_installed_ref (BZ_FLATPAK_ENTRY (entry));
-
- if (!is_installed_ref)
+ self->installable++;
+ state_flags |= ENTRY_INSTALLABLE;
+ if (!bz_entry_is_holding (entry))
{
- self->installable++;
- if (!bz_entry_is_holding (entry))
- {
- self->installable_available++;
- g_object_notify_by_pspec (G_OBJECT (self), props[PROP_INSTALLABLE_AND_AVAILABLE]);
- }
- g_object_notify_by_pspec (G_OBJECT (self), props[PROP_INSTALLABLE]);
+ self->installable_available++;
+ state_flags |= ENTRY_INSTALLABLE_AVAILABLE;
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_INSTALLABLE_AND_AVAILABLE]);
}
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_INSTALLABLE]);
}
}
+ if (existing != G_MAXUINT)
+ g_array_index (self->state_flags, gint32, existing) = state_flags;
+ else
+ g_array_append_val (self->state_flags, state_flags);
- if (!is_addon && is_searchable && !self->searchable)
+ if (!is_addon && is_searchable)
self->searchable = TRUE;
}
@@ -1206,38 +1244,56 @@ installed_changed (BzEntryGroup *self,
BzEntry *entry)
{
g_autoptr (GMutexLocker) locker = NULL;
- gboolean is_installed_ref = FALSE;
+ gboolean reinstallable = FALSE;
const char *unique_id = NULL;
const char *version = NULL;
guint index = 0;
+ gint32 state_flags = 0;
locker = g_mutex_locker_new (&self->mutex);
- if (BZ_IS_FLATPAK_ENTRY (entry))
- is_installed_ref = bz_flatpak_entry_is_installed_ref (BZ_FLATPAK_ENTRY (entry));
-
- unique_id = bz_entry_get_unique_id (entry);
- version = bz_entry_get_installed_version (entry);
- index = gtk_string_list_find (self->unique_ids, unique_id);
-
- if (index != G_MAXUINT)
- {
- gtk_string_list_splice (self->installed_versions, index, 1,
- (const char *const[]){
- version != NULL ? version : "",
- NULL });
- }
+ reinstallable = bz_entry_is_reinstallable (entry);
+ unique_id = bz_entry_get_unique_id (entry);
+ version = bz_entry_get_installed_version (entry);
+ index = gtk_string_list_find (self->unique_ids, unique_id);
+ if (index == G_MAXUINT)
+ return;
+ state_flags = g_array_index (self->state_flags, gint32, index);
+ gtk_string_list_splice (self->installed_versions, index, 1,
+ (const char *const[]){
+ version != NULL ? version : "",
+ NULL });
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_INSTALLED_VERSIONS]);
if (bz_entry_is_installed (entry))
{
- self->installable--;
- self->removable++;
+ if (state_flags & ENTRY_INSTALLABLE)
+ {
+ self->installable--;
+ state_flags &= ~ENTRY_INSTALLABLE;
+ }
+
+ if (!(state_flags & ENTRY_REMOVABLE))
+ {
+ self->removable++;
+ state_flags |= ENTRY_REMOVABLE;
+ }
+
if (!bz_entry_is_holding (entry))
{
- self->installable_available--;
- self->removable_available++;
+ if (state_flags & ENTRY_INSTALLABLE_AVAILABLE)
+ {
+ self->installable_available--;
+ state_flags &= ~ENTRY_INSTALLABLE_AVAILABLE;
+ }
+
+ if (!(state_flags & ENTRY_REMOVABLE_AVAILABLE))
+ {
+ self->removable_available++;
+ state_flags |= ENTRY_REMOVABLE_AVAILABLE;
+ }
+
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_INSTALLABLE_AND_AVAILABLE]);
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_REMOVABLE_AND_AVAILABLE]);
}
@@ -1246,25 +1302,48 @@ installed_changed (BzEntryGroup *self,
}
else
{
- self->removable--;
- if (!is_installed_ref)
- self->installable++;
+ if (state_flags & ENTRY_REMOVABLE)
+ {
+ self->removable--;
+ state_flags &= ~ENTRY_REMOVABLE;
+ }
+
+ if (reinstallable)
+ {
+ if (!(state_flags & ENTRY_INSTALLABLE))
+ {
+ self->installable++;
+ state_flags |= ENTRY_INSTALLABLE;
+ }
+ }
if (!bz_entry_is_holding (entry))
{
- self->removable_available--;
- if (!is_installed_ref)
- self->installable_available++;
+ if (state_flags & ENTRY_REMOVABLE_AVAILABLE)
+ {
+ self->removable_available--;
+ state_flags &= ~ENTRY_REMOVABLE_AVAILABLE;
+ }
+
+ if (reinstallable)
+ {
+ if (!(state_flags & ENTRY_INSTALLABLE_AVAILABLE))
+ {
+ self->installable_available++;
+ state_flags |= ENTRY_INSTALLABLE_AVAILABLE;
+ }
+ }
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_REMOVABLE_AND_AVAILABLE]);
- if (!is_installed_ref)
+ if (reinstallable)
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_INSTALLABLE_AND_AVAILABLE]);
}
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_REMOVABLE]);
- if (!is_installed_ref)
+ if (reinstallable)
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_INSTALLABLE]);
}
+ g_array_index (self->state_flags, gint32, index) = state_flags;
dex_clear (&self->user_data_size_future);
self->user_data_size = 0;
@@ -1277,23 +1356,60 @@ holding_changed (BzEntryGroup *self,
BzEntry *entry)
{
g_autoptr (GMutexLocker) locker = NULL;
+ gboolean reinstallable = FALSE;
+ const char *unique_id = NULL;
+ guint index = 0;
+ gint32 state_flags = 0;
locker = g_mutex_locker_new (&self->mutex);
+ reinstallable = bz_entry_is_reinstallable (entry);
+
+ unique_id = bz_entry_get_unique_id (entry);
+ index = gtk_string_list_find (self->unique_ids, unique_id);
+ if (index == G_MAXUINT)
+ return;
+ state_flags = g_array_index (self->state_flags, gint32, index);
+
if (bz_entry_is_holding (entry))
{
if (bz_entry_is_installed (entry))
- self->removable_available--;
+ {
+ if (state_flags & ENTRY_REMOVABLE_AVAILABLE)
+ {
+ self->removable_available--;
+ state_flags &= ~ENTRY_REMOVABLE_AVAILABLE;
+ }
+ }
else
- self->installable_available--;
+ {
+ if (state_flags & ENTRY_INSTALLABLE_AVAILABLE)
+ {
+ self->installable_available--;
+ state_flags &= ~ENTRY_INSTALLABLE_AVAILABLE;
+ }
+ }
}
else
{
if (bz_entry_is_installed (entry))
- self->removable_available++;
- else
- self->installable_available++;
+ {
+ if (!(state_flags & ENTRY_REMOVABLE_AVAILABLE))
+ {
+ self->removable_available++;
+ state_flags |= ENTRY_REMOVABLE_AVAILABLE;
+ }
+ }
+ else if (reinstallable)
+ {
+ if (!(state_flags & ENTRY_INSTALLABLE_AVAILABLE))
+ {
+ self->installable_available++;
+ state_flags |= ENTRY_INSTALLABLE_AVAILABLE;
+ }
+ }
}
+ g_array_index (self->state_flags, gint32, index) = state_flags;
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_REMOVABLE_AND_AVAILABLE]);
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_INSTALLABLE_AND_AVAILABLE]);
diff --git a/src/bz-entry.c b/src/bz-entry.c
index c389c03c..2ec4b7b6 100644
--- a/src/bz-entry.c
+++ b/src/bz-entry.c
@@ -74,6 +74,7 @@ typedef struct
gint hold;
gboolean installed;
char *installed_version;
+ gboolean reinstallable;
gboolean searchable;
guint kinds;
@@ -141,6 +142,7 @@ enum
PROP_HOLDING,
PROP_INSTALLED,
PROP_INSTALLED_VERSION,
+ PROP_REINSTALLABLE,
PROP_SEARCHABLE,
PROP_KINDS,
PROP_ADDONS,
@@ -273,6 +275,9 @@ bz_entry_get_property (GObject *object,
case PROP_INSTALLED_VERSION:
g_value_set_string (value, priv->installed_version);
break;
+ case PROP_REINSTALLABLE:
+ g_value_set_boolean (value, priv->reinstallable);
+ break;
case PROP_SEARCHABLE:
g_value_set_boolean (value, priv->searchable);
break;
@@ -459,6 +464,9 @@ bz_entry_set_property (GObject *object,
g_clear_pointer (&priv->installed_version, g_free);
priv->installed_version = g_value_dup_string (value);
break;
+ case PROP_REINSTALLABLE:
+ priv->reinstallable = g_value_get_boolean (value);
+ break;
case PROP_SEARCHABLE:
priv->searchable = g_value_get_boolean (value);
break;
@@ -693,6 +701,12 @@ bz_entry_class_init (BzEntryClass *klass)
NULL, NULL, NULL,
G_PARAM_READWRITE);
+ props[PROP_REINSTALLABLE] =
+ g_param_spec_boolean (
+ "reinstallable",
+ NULL, NULL, TRUE,
+ G_PARAM_READWRITE);
+
props[PROP_SEARCHABLE] =
g_param_spec_boolean (
"searchable",
@@ -1050,6 +1064,7 @@ bz_entry_init (BzEntry *self)
BzEntryPrivate *priv = bz_entry_get_instance_private (self);
priv->hold = 0;
+ priv->reinstallable = TRUE;
priv->searchable = TRUE;
priv->favorites_count = -1;
}
@@ -1065,6 +1080,7 @@ bz_entry_real_serialize (BzSerializable *serializable,
if (priv->installed_version != NULL)
g_variant_builder_add (builder, "{sv}", "installed-version", g_variant_new_string (priv->installed_version));
g_variant_builder_add (builder, "{sv}", "kinds", g_variant_new_uint32 (priv->kinds));
+ g_variant_builder_add (builder, "{sv}", "reinstallable", g_variant_new_boolean (priv->reinstallable));
g_variant_builder_add (builder, "{sv}", "searchable", g_variant_new_boolean (priv->searchable));
if (priv->addons != NULL)
{
@@ -1412,6 +1428,8 @@ bz_entry_real_deserialize (BzSerializable *serializable,
priv->installed_version = g_variant_dup_string (value, NULL);
else if (g_strcmp0 (key, "kinds") == 0)
priv->kinds = g_variant_get_uint32 (value);
+ else if (g_strcmp0 (key, "reinstallable") == 0)
+ priv->reinstallable = g_variant_get_boolean (value);
else if (g_strcmp0 (key, "searchable") == 0)
priv->searchable = g_variant_get_boolean (value);
else if (g_strcmp0 (key, "addons") == 0)
@@ -1808,6 +1826,17 @@ bz_entry_is_of_kinds (BzEntry *self,
return (priv->kinds & kinds) == kinds;
}
+gboolean
+bz_entry_is_reinstallable (BzEntry *self)
+{
+ BzEntryPrivate *priv = NULL;
+
+ g_return_val_if_fail (BZ_IS_ENTRY (self), TRUE);
+ priv = bz_entry_get_instance_private (self);
+
+ return priv->reinstallable;
+}
+
gboolean
bz_entry_is_searchable (BzEntry *self)
{
diff --git a/src/bz-entry.h b/src/bz-entry.h
index 93ab94f8..5d250750 100644
--- a/src/bz-entry.h
+++ b/src/bz-entry.h
@@ -24,8 +24,8 @@
#include
#include
-#include "bz-repository.h"
#include "bz-category-flags.h"
+#include "bz-repository.h"
G_BEGIN_DECLS
@@ -97,6 +97,9 @@ void
bz_entry_set_installed (BzEntry *self,
gboolean installed);
+gboolean
+bz_entry_is_reinstallable (BzEntry *self);
+
gboolean
bz_entry_is_searchable (BzEntry *self);
diff --git a/src/bz-flatpak-entry.c b/src/bz-flatpak-entry.c
index f86bd57c..e36b3f5e 100644
--- a/src/bz-flatpak-entry.c
+++ b/src/bz-flatpak-entry.c
@@ -53,6 +53,7 @@ struct _BzFlatpakEntry
char *application_command;
char *runtime_name;
char *addon_extension_of_ref;
+ char *bundle_path;
BzResult *runtime_result;
FlatpakRef *ref;
@@ -74,6 +75,7 @@ enum
PROP_USER,
PROP_FLATPAK_NAME,
PROP_IS_BUNDLE,
+ PROP_BUNDLE_PATH,
PROP_FLATPAK_ID,
PROP_FLATPAK_VERSION,
PROP_APPLICATION_NAME,
@@ -132,6 +134,9 @@ bz_flatpak_entry_get_property (GObject *object,
case PROP_IS_BUNDLE:
g_value_set_boolean (value, self->is_bundle);
break;
+ case PROP_BUNDLE_PATH:
+ g_value_set_string (value, self->bundle_path);
+ break;
case PROP_APPLICATION_RUNTIME:
g_value_set_string (value, self->application_runtime);
break;
@@ -172,6 +177,7 @@ bz_flatpak_entry_set_property (GObject *object,
case PROP_APPLICATION_COMMAND:
case PROP_RUNTIME_NAME:
case PROP_ADDON_OF_REF:
+ case PROP_BUNDLE_PATH:
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
@@ -224,6 +230,12 @@ bz_flatpak_entry_class_init (BzFlatpakEntryClass *klass)
FALSE,
G_PARAM_READABLE);
+ props[PROP_BUNDLE_PATH] =
+ g_param_spec_string (
+ "bundle-path",
+ NULL, NULL, NULL,
+ G_PARAM_READABLE);
+
props[PROP_APPLICATION_RUNTIME] =
g_param_spec_string (
"application-runtime",
@@ -270,7 +282,10 @@ bz_flatpak_entry_real_serialize (BzSerializable *serializable,
BzFlatpakEntry *self = BZ_FLATPAK_ENTRY (serializable);
g_variant_builder_add (builder, "{sv}", "user", g_variant_new_boolean (self->user));
+ g_variant_builder_add (builder, "{sv}", "is-bundle", g_variant_new_boolean (self->is_bundle));
g_variant_builder_add (builder, "{sv}", "is-installed-ref", g_variant_new_boolean (self->is_installed_ref));
+ if (self->bundle_path != NULL)
+ g_variant_builder_add (builder, "{sv}", "bundle-path", g_variant_new_string (self->bundle_path));
if (self->flatpak_name != NULL)
g_variant_builder_add (builder, "{sv}", "flatpak-name", g_variant_new_string (self->flatpak_name));
if (self->flatpak_id != NULL)
@@ -312,8 +327,12 @@ bz_flatpak_entry_real_deserialize (BzSerializable *serializable,
if (g_strcmp0 (key, "user") == 0)
self->user = g_variant_get_boolean (value);
+ else if (g_strcmp0 (key, "is-bundle") == 0)
+ self->is_bundle = g_variant_get_boolean (value);
else if (g_strcmp0 (key, "is-installed-ref") == 0)
self->is_installed_ref = g_variant_get_boolean (value);
+ else if (g_strcmp0 (key, "bundle-path") == 0)
+ self->bundle_path = g_variant_dup_string (value, NULL);
else if (g_strcmp0 (key, "flatpak-name") == 0)
self->flatpak_name = g_variant_dup_string (value, NULL);
else if (g_strcmp0 (key, "flatpak-id") == 0)
@@ -401,6 +420,7 @@ bz_flatpak_entry_new_for_ref (FlatpakRef *ref,
g_autoptr (GdkPaintable) icon_paintable = NULL;
g_autoptr (BzAppPermissions) permissions = NULL;
gboolean searchable = FALSE;
+ gboolean reinstallable = FALSE;
g_return_val_if_fail (FLATPAK_IS_REF (ref), NULL);
g_return_val_if_fail (FLATPAK_IS_REMOTE_REF (ref) || FLATPAK_IS_BUNDLE_REF (ref) || FLATPAK_IS_INSTALLED_REF (ref), NULL);
@@ -412,6 +432,15 @@ bz_flatpak_entry_new_for_ref (FlatpakRef *ref,
self->is_installed_ref = FLATPAK_IS_INSTALLED_REF (ref);
self->ref = g_object_ref (ref);
+ if (FLATPAK_IS_BUNDLE_REF (ref))
+ {
+ GFile *file = NULL;
+
+ file = flatpak_bundle_ref_get_file (FLATPAK_BUNDLE_REF (ref));
+ if (file != NULL)
+ self->bundle_path = g_file_get_path (file);
+ }
+
key_file = g_key_file_new ();
if (FLATPAK_IS_REMOTE_REF (ref))
bytes = flatpak_remote_ref_get_metadata (FLATPAK_REMOTE_REF (ref));
@@ -613,7 +642,8 @@ bz_flatpak_entry_new_for_ref (FlatpakRef *ref,
if (permissions == NULL)
return NULL;
- searchable = !FLATPAK_IS_INSTALLED_REF (ref);
+ searchable = !FLATPAK_IS_INSTALLED_REF (ref);
+ reinstallable = !FLATPAK_IS_INSTALLED_REF (ref);
g_object_set (
self,
@@ -629,6 +659,7 @@ bz_flatpak_entry_new_for_ref (FlatpakRef *ref,
"icon-paintable", icon_paintable,
"permissions", permissions,
"searchable", searchable,
+ "reinstallable", reinstallable,
NULL);
return g_steal_pointer (&self);
@@ -642,7 +673,8 @@ bz_flatpak_ref_parts_format_unique (const char *origin,
return g_strdup_printf (
"FLATPAK-%s::%s::%s",
user ? "USER" : "SYSTEM",
- origin, fmt);
+ origin != NULL ? origin : "bundle",
+ fmt);
}
char *
@@ -656,8 +688,6 @@ bz_flatpak_ref_format_unique (FlatpakRef *ref,
if (FLATPAK_IS_REMOTE_REF (ref))
origin = flatpak_remote_ref_get_remote_name (FLATPAK_REMOTE_REF (ref));
- else if (FLATPAK_IS_BUNDLE_REF (ref))
- origin = flatpak_bundle_ref_get_origin (FLATPAK_BUNDLE_REF (ref));
else if (FLATPAK_IS_INSTALLED_REF (ref))
origin = flatpak_installed_ref_get_origin (FLATPAK_INSTALLED_REF (ref));
@@ -759,6 +789,13 @@ bz_flatpak_entry_is_installed_ref (BzFlatpakEntry *self)
return self->is_installed_ref;
}
+const char *
+bz_flatpak_entry_get_bundle_path (BzFlatpakEntry *self)
+{
+ g_return_val_if_fail (BZ_IS_FLATPAK_ENTRY (self), NULL);
+ return self->bundle_path;
+}
+
gboolean
bz_flatpak_entry_launch (BzFlatpakEntry *self,
BzFlatpakInstance *flatpak,
@@ -814,6 +851,7 @@ clear_entry (BzFlatpakEntry *self)
g_clear_pointer (&self->application_command, g_free);
g_clear_pointer (&self->runtime_name, g_free);
g_clear_pointer (&self->addon_extension_of_ref, g_free);
+ g_clear_pointer (&self->bundle_path, g_free);
g_clear_object (&self->runtime_result);
}
diff --git a/src/bz-flatpak-entry.h b/src/bz-flatpak-entry.h
index 5c85e1c2..41aacd48 100644
--- a/src/bz-flatpak-entry.h
+++ b/src/bz-flatpak-entry.h
@@ -63,6 +63,9 @@ bz_flatpak_entry_is_bundle (BzFlatpakEntry *self);
gboolean
bz_flatpak_entry_is_installed_ref (BzFlatpakEntry *self);
+const char *
+bz_flatpak_entry_get_bundle_path (BzFlatpakEntry *self);
+
const char *
bz_flatpak_entry_get_addon_extension_of_ref (BzFlatpakEntry *self);
diff --git a/src/bz-flatpak-instance.c b/src/bz-flatpak-instance.c
index d4fc7fb8..353bb820 100644
--- a/src/bz-flatpak-instance.c
+++ b/src/bz-flatpak-instance.c
@@ -169,11 +169,19 @@ BZ_DEFINE_DATA (
static DexFuture *
retrieve_refs_for_remote_fiber (RetrieveRefsForRemoteData *data);
-static void
-gather_refs_update_progress (const char *status,
- guint progress,
- gboolean estimating,
- GatherRefsData *data);
+static DexFuture *
+retrieve_refs_for_enumerable_remote (BzFlatpakInstance *self,
+ GCancellable *cancellable,
+ const char *remote_name,
+ FlatpakInstallation *installation,
+ FlatpakRemote *remote);
+
+static DexFuture *
+retrieve_refs_for_noenumerable_remote (BzFlatpakInstance *self,
+ GCancellable *cancellable,
+ const char *remote_name,
+ FlatpakInstallation *installation,
+ FlatpakRemote *remote);
BZ_DEFINE_DATA (
transaction,
@@ -256,6 +264,20 @@ static void
transaction_progress_changed (FlatpakTransactionProgress *object,
TransactionOperationData *data);
+BZ_DEFINE_DATA (
+ transaction_operation_done,
+ TransactionOperationDone,
+ {
+ TransactionData *parent;
+ FlatpakTransaction *transaction;
+ FlatpakTransactionOperation *operation;
+ },
+ BZ_RELEASE_DATA (parent, transaction_data_unref);
+ BZ_RELEASE_DATA (transaction, g_object_unref);
+ BZ_RELEASE_DATA (operation, g_object_unref));
+static DexFuture *
+transaction_operation_done_fiber (TransactionOperationDoneData *data);
+
static void
installation_event (BzFlatpakInstance *self,
GFile *file,
@@ -949,11 +971,15 @@ ensure_flathub_fiber (EnsureFlathubData *data)
static DexFuture *
load_local_ref_fiber (LoadLocalRefData *data)
{
- GFile *file = data->file;
- gboolean result = FALSE;
- g_autoptr (GError) local_error = NULL;
- g_autofree char *uri = NULL;
- g_autofree char *path = NULL;
+ g_autoptr (BzFlatpakInstance) self = NULL;
+ GFile *file = data->file;
+ GCancellable *cancellable = data->cancellable;
+ gboolean result = FALSE;
+ g_autoptr (GError) local_error = NULL;
+ g_autofree char *uri = NULL;
+ g_autofree char *path = NULL;
+
+ bz_weak_get_or_return_reject (self, data->self);
uri = g_file_get_uri (file);
path = g_file_get_path (file);
@@ -1017,16 +1043,30 @@ load_local_ref_fiber (LoadLocalRefData *data)
"Failed to load locate \"Name\" key in flatpakref '%s': %s",
uri, local_error->message);
+ {
+ g_autoptr (BzBackendNotification) notif = NULL;
+
+ notif = bz_backend_notification_new ();
+ bz_backend_notification_set_kind (notif, BZ_BACKEND_NOTIFICATION_KIND_PRESENT_ID);
+ bz_backend_notification_set_generic_id (notif, name);
+
+ send_notif_all (self, notif, TRUE);
+ }
+
return dex_future_new_take_string (g_steal_pointer (&name));
}
else
/* This is a bundle ref */
{
- g_autoptr (FlatpakBundleRef) bref = NULL;
- g_autoptr (BzFlatpakEntry) entry = NULL;
- g_autoptr (GBytes) appstream_gz = NULL;
- g_autoptr (GBytes) appstream = NULL;
- g_autoptr (AsComponent) component = NULL;
+ g_autoptr (FlatpakBundleRef) bref = NULL;
+ const char *name = NULL;
+ const char *origin = NULL;
+ FlatpakInstallation *add_to_installation = NULL;
+ g_autoptr (FlatpakRemote) remote = NULL;
+ g_autoptr (BzFlatpakEntry) entry = NULL;
+ g_autoptr (GBytes) appstream_gz = NULL;
+ g_autoptr (GBytes) appstream = NULL;
+ g_autoptr (AsComponent) component = NULL;
if (path == NULL)
return dex_future_new_reject (
@@ -1044,6 +1084,126 @@ load_local_ref_fiber (LoadLocalRefData *data)
path,
local_error->message);
+ name = flatpak_ref_get_name (FLATPAK_REF (bref));
+ origin = flatpak_bundle_ref_get_origin (bref);
+
+ if (self->system != NULL)
+ add_to_installation = self->system;
+ else if (self->user != NULL)
+ add_to_installation = self->user;
+
+ /* First check if we already should have the origin remote
+ installed */
+ if (self->system != NULL)
+ {
+ g_autoptr (GPtrArray) remotes = NULL;
+
+ remotes = flatpak_installation_list_remotes (
+ self->system, NULL, NULL);
+ if (remotes != NULL)
+ {
+ for (guint i = 0; i < remotes->len; i++)
+ {
+ FlatpakRemote *existing = NULL;
+ const char *url = NULL;
+
+ existing = g_ptr_array_index (remotes, i);
+ url = flatpak_remote_get_url (existing);
+
+ if (url != NULL &&
+ g_strcmp0 (url, origin) == 0)
+ {
+ remote = g_object_ref (existing);
+ add_to_installation = NULL;
+ break;
+ }
+ }
+ }
+ }
+ if (self->user != NULL)
+ {
+ g_autoptr (GPtrArray) remotes = NULL;
+
+ remotes = flatpak_installation_list_remotes (
+ self->user, NULL, NULL);
+ if (remotes != NULL)
+ {
+ for (guint i = 0; i < remotes->len; i++)
+ {
+ FlatpakRemote *existing = NULL;
+ const char *url = NULL;
+
+ existing = g_ptr_array_index (remotes, i);
+ url = flatpak_remote_get_url (existing);
+
+ if (url != NULL &&
+ g_strcmp0 (url, origin) == 0)
+ {
+ remote = g_object_ref (existing);
+ add_to_installation = NULL;
+ break;
+ }
+ }
+ }
+ }
+
+ if (add_to_installation != NULL)
+ {
+ g_autoptr (FlatpakRemote) config_remote = NULL;
+ g_autofree char *remote_name = NULL;
+
+ /* Configure and sync the new remote */
+ remote_name = g_strdup_printf ("%s-bazaar-origin", name);
+
+ config_remote = flatpak_remote_new (remote_name);
+ flatpak_remote_set_url (config_remote, origin);
+ flatpak_remote_set_disabled (config_remote, FALSE);
+ flatpak_remote_set_noenumerate (config_remote, FALSE);
+ flatpak_remote_set_gpg_verify (config_remote, TRUE);
+
+ result = flatpak_installation_add_remote (
+ add_to_installation, config_remote, FALSE, cancellable, NULL);
+ if (result)
+ {
+ {
+ g_autoptr (BzBackendNotification) notif = NULL;
+
+ notif = bz_backend_notification_new ();
+ bz_backend_notification_set_kind (notif, BZ_BACKEND_NOTIFICATION_KIND_INVALIDATE_REMOTES);
+
+ send_notif_all (self, notif, TRUE);
+ }
+
+ remote = flatpak_installation_get_remote_by_name (
+ add_to_installation, remote_name, cancellable, NULL);
+ if (remote != NULL)
+ {
+ {
+ g_autoptr (BzBackendNotification) notif = NULL;
+
+ notif = bz_backend_notification_new ();
+ bz_backend_notification_set_kind (notif, BZ_BACKEND_NOTIFICATION_KIND_REMOTE_SYNC_START);
+ bz_backend_notification_set_remote_name (notif, remote_name);
+
+ send_notif_all (self, notif, TRUE);
+ }
+ result = dex_await (
+ retrieve_refs_for_enumerable_remote (
+ self, cancellable, remote_name, add_to_installation, remote),
+ NULL);
+ {
+ g_autoptr (BzBackendNotification) notif = NULL;
+
+ notif = bz_backend_notification_new ();
+ bz_backend_notification_set_kind (notif, BZ_BACKEND_NOTIFICATION_KIND_REMOTE_SYNC_FINISH);
+ bz_backend_notification_set_remote_name (notif, remote_name);
+
+ send_notif_all (self, notif, TRUE);
+ }
+ }
+ }
+ }
+
appstream_gz = flatpak_bundle_ref_get_appstream (bref);
if (appstream_gz != NULL)
{
@@ -1090,7 +1250,7 @@ load_local_ref_fiber (LoadLocalRefData *data)
entry = bz_flatpak_entry_new_for_ref (
FLATPAK_REF (bref),
- NULL,
+ remote,
FALSE,
component,
NULL,
@@ -1247,22 +1407,13 @@ retrieve_remote_refs_fiber (GatherRefsData *data)
"%s", error_string->str);
}
-static void
-gather_refs_update_progress (const char *status,
- guint progress,
- gboolean estimating,
- GatherRefsData *data)
-{
-}
-
static DexFuture *
-retrieve_refs_for_enumerable_remote (RetrieveRefsForRemoteData *data,
- const char *remote_name,
- FlatpakInstallation *installation,
- FlatpakRemote *remote)
+retrieve_refs_for_enumerable_remote (BzFlatpakInstance *self,
+ GCancellable *cancellable,
+ const char *remote_name,
+ FlatpakInstallation *installation,
+ FlatpakRemote *remote)
{
- g_autoptr (BzFlatpakInstance) self = NULL;
- GCancellable *cancellable = data->parent->cancellable;
g_autoptr (GError) local_error = NULL;
gboolean result = FALSE;
g_autoptr (GFile) appstream_dir = NULL;
@@ -1276,8 +1427,6 @@ retrieve_refs_for_enumerable_remote (RetrieveRefsForRemoteData *data,
g_autoptr (GHashTable) component_hash = NULL;
g_autoptr (GPtrArray) refs = NULL;
- bz_weak_get_or_return_reject (self, data->parent->self);
-
g_debug ("Remote '%s' is enumerable, listing all remote refs", remote_name);
result = flatpak_installation_update_remote_sync (
@@ -1296,10 +1445,7 @@ retrieve_refs_for_enumerable_remote (RetrieveRefsForRemoteData *data,
result = flatpak_installation_update_appstream_full_sync (
installation,
remote_name,
- NULL,
- (FlatpakProgressCallback) gather_refs_update_progress,
- data,
- NULL,
+ NULL, NULL, NULL, NULL,
cancellable,
&local_error);
if (!result)
@@ -1479,19 +1625,16 @@ retrieve_refs_for_enumerable_remote (RetrieveRefsForRemoteData *data,
}
static DexFuture *
-retrieve_refs_for_noenumerable_remote (RetrieveRefsForRemoteData *data,
- const char *remote_name,
- FlatpakInstallation *installation,
- FlatpakRemote *remote)
+retrieve_refs_for_noenumerable_remote (BzFlatpakInstance *self,
+ GCancellable *cancellable,
+ const char *remote_name,
+ FlatpakInstallation *installation,
+ FlatpakRemote *remote)
{
- g_autoptr (BzFlatpakInstance) self = NULL;
- GCancellable *cancellable = data->parent->cancellable;
g_autoptr (GError) local_error = NULL;
g_autoptr (GPtrArray) installed_apps = NULL;
guint matched = 0;
- bz_weak_get_or_return_reject (self, data->parent->self);
-
installed_apps = flatpak_installation_list_installed_refs_by_kind (
installation,
FLATPAK_REF_KIND_APP,
@@ -1628,10 +1771,12 @@ retrieve_refs_for_remote_fiber (RetrieveRefsForRemoteData *data)
if (is_noenumerate)
#endif
ret = retrieve_refs_for_noenumerable_remote (
- data, remote_name, installation, remote);
+ self, data->parent->cancellable,
+ remote_name, installation, remote);
else
ret = retrieve_refs_for_enumerable_remote (
- data, remote_name, installation, remote);
+ self, data->parent->cancellable,
+ remote_name, installation, remote);
{
g_autoptr (BzBackendNotification) notif = NULL;
@@ -1908,15 +2053,17 @@ transaction_fiber (TransactionData *data)
for (guint i = 0; i < installations->len; i++)
{
BzFlatpakEntry *entry = NULL;
+ const char *bundle_path = NULL;
FlatpakRef *ref = NULL;
gboolean is_user = FALSE;
g_autofree char *ref_fmt = NULL;
g_autoptr (FlatpakTransaction) transaction = NULL;
- entry = g_ptr_array_index (installations, i);
- ref = bz_flatpak_entry_get_ref (entry);
- is_user = bz_flatpak_entry_is_user (BZ_FLATPAK_ENTRY (entry));
- ref_fmt = flatpak_ref_format_ref (ref);
+ entry = g_ptr_array_index (installations, i);
+ bundle_path = bz_flatpak_entry_get_bundle_path (entry);
+ ref = bz_flatpak_entry_get_ref (entry);
+ is_user = bz_flatpak_entry_is_user (BZ_FLATPAK_ENTRY (entry));
+ ref_fmt = flatpak_ref_format_ref (ref);
if ((is_user && self->user == NULL) ||
(!is_user && self->system == NULL))
@@ -1945,12 +2092,25 @@ transaction_fiber (TransactionData *data)
local_error->message);
}
- result = flatpak_transaction_add_install (
- transaction,
- bz_entry_get_remote_repo_name (BZ_ENTRY (entry)),
- ref_fmt,
- NULL,
- &local_error);
+ if (bundle_path != NULL)
+ /* Prioritize bundle installation */
+ {
+ g_autoptr (GFile) file = NULL;
+
+ file = g_file_new_for_path (bundle_path);
+ result = flatpak_transaction_add_install_bundle (
+ transaction,
+ file,
+ NULL,
+ &local_error);
+ }
+ else
+ result = flatpak_transaction_add_install (
+ transaction,
+ bz_entry_get_remote_repo_name (BZ_ENTRY (entry)),
+ ref_fmt,
+ NULL,
+ &local_error);
if (!result)
{
dex_channel_close_send (channel);
@@ -2383,24 +2543,15 @@ transaction_operation_done (FlatpakTransaction *transaction,
gint result,
TransactionData *data)
{
- g_autoptr (BzFlatpakInstance) self = NULL;
- g_autoptr (BzBackendTransactionOpPayload) payload = NULL;
- FlatpakTransactionOperationType op_type = 0;
- BzBackendNotificationKind notif_kind = 0;
- const char *origin = NULL;
- const char *ref = NULL;
- gboolean is_user = FALSE;
- g_autofree char *unique_id = NULL;
- g_autoptr (BzBackendNotification) notif = NULL;
- const char *version = NULL;
- FlatpakInstallation *installation = NULL;
- g_autoptr (FlatpakInstalledRef) iref = NULL;
- g_autoptr (GError) local_error = NULL;
- g_autoptr (FlatpakRef) parsed_ref = NULL;
+ g_autoptr (GMutexLocker) locker = NULL;
+ g_autoptr (BzFlatpakInstance) self = NULL;
+ g_autoptr (BzBackendTransactionOpPayload) payload = NULL;
+ g_autoptr (TransactionOperationDoneData) future_data = NULL;
+ g_autoptr (DexFuture) future = NULL;
bz_weak_get_or_return (self, data->self);
+ locker = g_mutex_locker_new (&data->mutex);
- g_mutex_lock (&data->mutex);
g_hash_table_replace (
data->op_to_progress_hash,
g_object_ref (operation),
@@ -2413,74 +2564,23 @@ transaction_operation_done (FlatpakTransaction *transaction,
dex_channel_send (
data->channel,
dex_future_new_for_object (payload)));
- g_mutex_unlock (&data->mutex);
if (result == FLATPAK_TRANSACTION_RESULT_NO_CHANGE)
return;
- op_type = flatpak_transaction_operation_get_operation_type (operation);
- switch (op_type)
- {
- case FLATPAK_TRANSACTION_OPERATION_INSTALL:
- case FLATPAK_TRANSACTION_OPERATION_INSTALL_BUNDLE:
- notif_kind = BZ_BACKEND_NOTIFICATION_KIND_INSTALL_DONE;
- break;
- case FLATPAK_TRANSACTION_OPERATION_UPDATE:
- notif_kind = BZ_BACKEND_NOTIFICATION_KIND_UPDATE_DONE;
- break;
- case FLATPAK_TRANSACTION_OPERATION_UNINSTALL:
- notif_kind = BZ_BACKEND_NOTIFICATION_KIND_REMOVE_DONE;
- break;
- case FLATPAK_TRANSACTION_OPERATION_LAST_TYPE:
- default:
- g_assert_not_reached ();
- }
-
- origin = flatpak_transaction_operation_get_remote (operation);
- ref = flatpak_transaction_operation_get_ref (operation);
- is_user = flatpak_transaction_get_installation (transaction) == self->user_interactive;
- unique_id = bz_flatpak_ref_parts_format_unique (origin, ref, is_user);
-
- if (notif_kind == BZ_BACKEND_NOTIFICATION_KIND_INSTALL_DONE ||
- notif_kind == BZ_BACKEND_NOTIFICATION_KIND_UPDATE_DONE)
- {
- installation = flatpak_transaction_get_installation (transaction);
-
- parsed_ref = flatpak_ref_parse (ref, &local_error);
- if (parsed_ref != NULL)
- {
- iref = flatpak_installation_get_installed_ref (
- installation,
- flatpak_ref_get_kind (parsed_ref),
- flatpak_ref_get_name (parsed_ref),
- flatpak_ref_get_arch (parsed_ref),
- flatpak_ref_get_branch (parsed_ref),
- NULL,
- &local_error);
-
- if (iref != NULL)
- version = flatpak_installed_ref_get_appdata_version (iref);
- else if (local_error != NULL)
- {
- g_warning ("Failed to get installed ref for version: %s", local_error->message);
- g_clear_error (&local_error);
- }
- }
- else if (local_error != NULL)
- {
- g_warning ("Failed to parse ref for version: %s", local_error->message);
- g_clear_error (&local_error);
- }
- }
-
- notif = bz_backend_notification_new ();
- bz_backend_notification_set_kind (notif, notif_kind);
- bz_backend_notification_set_unique_id (notif, unique_id);
+ future_data = transaction_operation_done_data_new ();
+ future_data->parent = transaction_data_ref (data);
+ future_data->transaction = g_object_ref (transaction);
+ future_data->operation = g_object_ref (operation);
- if (version != NULL && *version != '\0')
- bz_backend_notification_set_version (notif, version);
+ future = dex_scheduler_spawn (
+ self->scheduler,
+ bz_get_dex_stack_size (),
+ (DexFiberFunc) transaction_operation_done_fiber,
+ transaction_operation_done_data_ref (future_data),
+ transaction_operation_done_data_unref);
- send_notif_all (self, notif, TRUE);
+ g_ptr_array_add (data->send_futures, g_steal_pointer (&future));
}
static gboolean
@@ -2632,6 +2732,140 @@ transaction_progress_changed (FlatpakTransactionProgress *progress,
g_mutex_unlock (&parent->mutex);
}
+static DexFuture *
+transaction_operation_done_fiber (TransactionOperationDoneData *data)
+{
+ g_autoptr (BzFlatpakInstance) self = NULL;
+ FlatpakTransaction *transaction = data->transaction;
+ FlatpakTransactionOperation *operation = data->operation;
+ g_autoptr (GError) local_error = NULL;
+ FlatpakTransactionOperationType op_type = 0;
+ BzBackendNotificationKind notif_kind = 0;
+ const char *origin = NULL;
+ const char *ref = NULL;
+ gboolean is_user = FALSE;
+ g_autofree char *unique_id = NULL;
+ const char *version = NULL;
+ FlatpakInstallation *installation = NULL;
+ g_autoptr (FlatpakInstalledRef) iref = NULL;
+ g_autoptr (FlatpakRef) parsed_ref = NULL;
+
+ bz_weak_get_or_return_reject (self, data->parent->self);
+
+ op_type = flatpak_transaction_operation_get_operation_type (operation);
+ switch (op_type)
+ {
+ case FLATPAK_TRANSACTION_OPERATION_INSTALL:
+ case FLATPAK_TRANSACTION_OPERATION_INSTALL_BUNDLE:
+ notif_kind = BZ_BACKEND_NOTIFICATION_KIND_INSTALL_DONE;
+ break;
+ case FLATPAK_TRANSACTION_OPERATION_UPDATE:
+ notif_kind = BZ_BACKEND_NOTIFICATION_KIND_UPDATE_DONE;
+ break;
+ case FLATPAK_TRANSACTION_OPERATION_UNINSTALL:
+ notif_kind = BZ_BACKEND_NOTIFICATION_KIND_REMOVE_DONE;
+ break;
+ case FLATPAK_TRANSACTION_OPERATION_LAST_TYPE:
+ default:
+ g_assert_not_reached ();
+ }
+
+ installation = flatpak_transaction_get_installation (transaction);
+
+ origin = flatpak_transaction_operation_get_remote (operation);
+ ref = flatpak_transaction_operation_get_ref (operation);
+ is_user = installation == self->user_interactive;
+
+ unique_id = bz_flatpak_ref_parts_format_unique (origin, ref, is_user);
+
+ if (op_type == FLATPAK_TRANSACTION_OPERATION_INSTALL_BUNDLE)
+ {
+ g_autoptr (FlatpakRemote) remote = NULL;
+
+ {
+ g_autoptr (BzBackendNotification) notif = NULL;
+
+ notif = bz_backend_notification_new ();
+ bz_backend_notification_set_kind (notif, BZ_BACKEND_NOTIFICATION_KIND_INVALIDATE_REMOTES);
+
+ send_notif_all (self, notif, TRUE);
+ }
+
+ remote = flatpak_installation_get_remote_by_name (
+ installation, origin, NULL, NULL);
+ if (remote != NULL)
+ {
+ {
+ g_autoptr (BzBackendNotification) notif = NULL;
+
+ notif = bz_backend_notification_new ();
+ bz_backend_notification_set_kind (notif, BZ_BACKEND_NOTIFICATION_KIND_REMOTE_SYNC_START);
+ bz_backend_notification_set_remote_name (notif, origin);
+
+ send_notif_all (self, notif, TRUE);
+ }
+ dex_await (
+ retrieve_refs_for_noenumerable_remote (
+ self, NULL, origin, installation, remote),
+ NULL);
+ {
+ g_autoptr (BzBackendNotification) notif = NULL;
+
+ notif = bz_backend_notification_new ();
+ bz_backend_notification_set_kind (notif, BZ_BACKEND_NOTIFICATION_KIND_REMOTE_SYNC_FINISH);
+ bz_backend_notification_set_remote_name (notif, origin);
+
+ send_notif_all (self, notif, TRUE);
+ }
+ }
+ }
+
+ if (notif_kind == BZ_BACKEND_NOTIFICATION_KIND_INSTALL_DONE ||
+ notif_kind == BZ_BACKEND_NOTIFICATION_KIND_UPDATE_DONE)
+ {
+ parsed_ref = flatpak_ref_parse (ref, &local_error);
+ if (parsed_ref != NULL)
+ {
+ iref = flatpak_installation_get_installed_ref (
+ installation,
+ flatpak_ref_get_kind (parsed_ref),
+ flatpak_ref_get_name (parsed_ref),
+ flatpak_ref_get_arch (parsed_ref),
+ flatpak_ref_get_branch (parsed_ref),
+ NULL,
+ &local_error);
+
+ if (iref != NULL)
+ version = flatpak_installed_ref_get_appdata_version (iref);
+ else if (local_error != NULL)
+ {
+ g_warning ("Failed to get installed ref for version: %s", local_error->message);
+ g_clear_error (&local_error);
+ }
+ }
+ else if (local_error != NULL)
+ {
+ g_warning ("Failed to parse ref for version: %s", local_error->message);
+ g_clear_error (&local_error);
+ }
+ }
+
+ {
+ g_autoptr (BzBackendNotification) notif = NULL;
+
+ notif = bz_backend_notification_new ();
+ bz_backend_notification_set_kind (notif, notif_kind);
+ bz_backend_notification_set_unique_id (notif, unique_id);
+
+ if (version != NULL && *version != '\0')
+ bz_backend_notification_set_version (notif, version);
+
+ send_notif_all (self, notif, TRUE);
+ }
+
+ return dex_future_new_true ();
+}
+
static void
installation_event (BzFlatpakInstance *self,
GFile *file,
diff --git a/src/bz-full-view.blp b/src/bz-full-view.blp
index b2b3ce86..8ed3b044 100644
--- a/src/bz-full-view.blp
+++ b/src/bz-full-view.blp
@@ -95,11 +95,11 @@ template $BzFullView: Adw.Bin {
margin-bottom: 15;
spacing: 20;
- Adw.Banner {
- title: _("Installing .flatpak bundles is not yet supported");
- visible: bind template.ui-entry as <$BzResult>.object as <$BzFlatpakEntry>.is-bundle as ;
- revealed: true;
- }
+ // Adw.Banner {
+ // title: _("Installing .flatpak bundles is not yet supported");
+ // visible: bind template.ui-entry as <$BzResult>.object as <$BzFlatpakEntry>.is-bundle as ;
+ // revealed: true;
+ // }
Adw.Banner {
title: _("This is a local preview, some details may differ from the published listing");
diff --git a/src/bz-install-controls.c b/src/bz-install-controls.c
index 64451abc..e0c63e6b 100644
--- a/src/bz-install-controls.c
+++ b/src/bz-install-controls.c
@@ -40,6 +40,8 @@ struct _BzInstallControls
gboolean wide;
BzTransactionEntryTracker *tracker;
+ GListModel *all_trackers;
+
GtkWidget *install_button;
char *install_btn_class;
char *pride_class;
@@ -349,6 +351,12 @@ bz_install_controls_dispose (GObject *object)
pride_flag_changed,
self);
+ if (self->all_trackers != NULL)
+ g_signal_handlers_disconnect_by_func (
+ self->all_trackers,
+ on_all_trackers_changed,
+ self);
+
g_clear_object (&self->group);
g_clear_object (&self->state);
g_clear_object (&self->settings);
@@ -356,6 +364,7 @@ bz_install_controls_dispose (GObject *object)
g_clear_object (&self->install_button);
g_clear_pointer (&self->install_btn_class, g_free);
g_clear_pointer (&self->pride_class, g_free);
+ g_clear_object (&self->all_trackers);
G_OBJECT_CLASS (bz_install_controls_parent_class)->dispose (object);
}
@@ -609,30 +618,23 @@ bz_install_controls_set_state (BzInstallControls *self,
{
g_return_if_fail (BZ_IS_INSTALL_CONTROLS (self));
- if (self->state != NULL)
+ if (self->all_trackers != NULL)
{
- BzTransactionManager *old_mgr = NULL;
- g_autoptr (GListModel) all = NULL;
-
- old_mgr = bz_state_info_get_transaction_manager (self->state);
- if (old_mgr != NULL)
- g_object_get (old_mgr, "all-trackers", &all, NULL);
- if (all != NULL)
- g_signal_handlers_disconnect_by_func (all, on_all_trackers_changed, self);
+ g_signal_handlers_disconnect_by_func (self->all_trackers, on_all_trackers_changed, self);
+ g_clear_object (&self->all_trackers);
}
g_set_object (&self->state, state);
if (state != NULL)
{
- BzTransactionManager *mgr = NULL;
- g_autoptr (GListModel) all = NULL;
+ BzTransactionManager *mgr = NULL;
mgr = bz_state_info_get_transaction_manager (state);
if (mgr != NULL)
- g_object_get (mgr, "all-trackers", &all, NULL);
- if (all != NULL)
- g_signal_connect (all, "items-changed",
+ g_object_get (mgr, "all-trackers", &self->all_trackers, NULL);
+ if (self->all_trackers != NULL)
+ g_signal_connect (self->all_trackers, "items-changed",
G_CALLBACK (on_all_trackers_changed), self);
}
diff --git a/src/bz-transaction-dialog.c b/src/bz-transaction-dialog.c
index b645d813..0ecb8be0 100644
--- a/src/bz-transaction-dialog.c
+++ b/src/bz-transaction-dialog.c
@@ -42,8 +42,7 @@ should_skip_entry (BzEntry *entry,
if (bz_entry_is_holding (entry))
return TRUE;
- if (!remove && BZ_IS_FLATPAK_ENTRY (entry) &&
- bz_flatpak_entry_is_installed_ref (BZ_FLATPAK_ENTRY (entry)))
+ if (!remove && !bz_entry_is_reinstallable (entry))
return TRUE;
is_installed = bz_entry_is_installed (entry);
@@ -337,8 +336,7 @@ show_dialog_fiber (ShowDialogData *data)
g_autoptr (BzEntry) entry = NULL;
entry = g_list_model_get_item (G_LIST_MODEL (store), i - 1);
- if (BZ_IS_FLATPAK_ENTRY (entry) &&
- bz_flatpak_entry_is_installed_ref (BZ_FLATPAK_ENTRY (entry)) &&
+ if (!bz_entry_is_reinstallable (entry) &&
(!data->remove || !bz_entry_is_installed (entry)))
g_list_store_remove (store, i - 1);
}
diff --git a/src/meson.build b/src/meson.build
index 9671be6d..37f01d47 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -67,12 +67,13 @@ bz_sources = files(
'bz-async-texture.c',
'bz-auth-state.c',
'bz-backend.c',
+ 'bz-bundle-install-dialog.c',
'bz-category-flags.c',
'bz-category-tile.c',
'bz-content-provider.c',
'bz-context-row.c',
- 'bz-context-tile.c',
'bz-context-tile-callbacks.c',
+ 'bz-context-tile.c',
'bz-curated-app-tile.c',
'bz-curated-view.c',
'bz-data-graph.c',
@@ -137,8 +138,8 @@ bz_sources = files(
'bz-screenshots-carousel.c',
'bz-search-engine.c',
'bz-search-filter-popover.c',
- 'bz-search-pill-list.c',
'bz-search-page.c',
+ 'bz-search-pill-list.c',
'bz-section-view.c',
'bz-serializable.c',
'bz-share-list.c',
@@ -262,19 +263,20 @@ blueprints = custom_target('blueprints',
input: files(
'bz-addon-tile.blp',
'bz-addons-dialog.blp',
- 'bz-donations-dialog.blp',
'bz-age-rating-dialog.blp',
'bz-all-apps-page.blp',
'bz-app-size-dialog.blp',
'bz-app-tile.blp',
'bz-apps-page.blp',
'bz-appstream-description-render.blp',
+ 'bz-bundle-install-dialog.blp',
'bz-category-tile.blp',
'bz-context-tile.blp',
'bz-curated-app-tile.blp',
'bz-curated-view.blp',
'bz-decorated-screenshot.blp',
'bz-developer-badge.blp',
+ 'bz-donations-dialog.blp',
'bz-entry-inspector.blp',
'bz-entry-selection-row.blp',
'bz-error-dialog.blp',
@@ -302,8 +304,8 @@ blueprints = custom_target('blueprints',
'bz-safety-dialog.blp',
'bz-screenshot-page.blp',
'bz-screenshots-carousel.blp',
- 'bz-search-page.blp',
'bz-search-filter-popover.blp',
+ 'bz-search-page.blp',
'bz-section-view.blp',
'bz-stats-dialog.blp',
'bz-transaction-list-dialog.blp',