From fbfe6851fa6fa48bca39acbe68d2d65993092d8b Mon Sep 17 00:00:00 2001 From: Eva M Date: Thu, 16 Apr 2026 22:01:34 -0700 Subject: [PATCH 01/11] add bundle install support --- src/bz-application.c | 38 ++-- src/bz-backend-notification.txt | 2 +- src/bz-entry-group.c | 16 +- src/bz-flatpak-entry.c | 41 ++++- src/bz-flatpak-entry.h | 3 + src/bz-flatpak-instance.c | 298 +++++++++++++++++++++++++------- src/bz-full-view.blp | 10 +- src/bz-window.c | 8 +- 8 files changed, 320 insertions(+), 96 deletions(-) diff --git a/src/bz-application.c b/src/bz-application.c index a6039574..0feba7ab 100644 --- a/src/bz-application.c +++ b/src/bz-application.c @@ -1371,6 +1371,23 @@ respond_to_flatpak_fiber (RespondToFlatpakData *data) update_labels = TRUE; } break; + case BZ_BACKEND_NOTIFICATION_KIND_INVALIDATE_REMOTES: + { + g_autoptr (GListModel) repos = NULL; + + 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); + } + } + break; case BZ_BACKEND_NOTIFICATION_KIND_REMOTE_SYNC_START: { const char *remote_name = NULL; @@ -1499,11 +1516,12 @@ 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_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 (); }; @@ -1735,17 +1753,7 @@ open_flatpakref_fiber (OpenFlatpakrefData *data) value = dex_future_get_value (future, &local_error); if (value != NULL) - { - if (G_VALUE_HOLDS_OBJECT (value)) - { - BzEntry *entry = 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)); - } + open_generic_id (self, g_value_get_string (value)); else bz_show_error_for_widget (GTK_WIDGET (window), _ ("Failed to open .flatpakref"), local_error->message); diff --git a/src/bz-backend-notification.txt b/src/bz-backend-notification.txt index 3816118f..038dc6ed 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 include="bz-entry.h" diff --git a/src/bz-entry-group.c b/src/bz-entry-group.c index f8fe3815..f17a74a9 100644 --- a/src/bz-entry-group.c +++ b/src/bz-entry-group.c @@ -934,11 +934,11 @@ bz_entry_group_add (BzEntryGroup *self, } } - title = bz_entry_get_title (entry); - description = bz_entry_get_description (entry); - installed_size = bz_entry_get_installed_size (entry); - is_flathub = bz_entry_get_is_flathub (entry); - is_floss = bz_entry_get_is_foss (entry); + title = bz_entry_get_title (entry); + description = bz_entry_get_description (entry); + installed_size = bz_entry_get_installed_size (entry); + is_flathub = bz_entry_get_is_flathub (entry); + is_floss = bz_entry_get_is_foss (entry); if (is_addon) // You would not see any addon when the filter is on without this. is_verified = TRUE; @@ -970,8 +970,8 @@ bz_entry_group_add (BzEntryGroup *self, gtk_string_list_remove (self->unique_ids, existing); gtk_string_list_remove (self->installed_versions, existing); } - gtk_string_list_splice (self->unique_ids, 0, 0, (const char *const[]) { unique_id, NULL }); - gtk_string_list_splice (self->installed_versions, 0, 0, (const char *const[]) { installed_version != NULL ? installed_version : "", NULL }); + gtk_string_list_splice (self->unique_ids, 0, 0, (const char *const[]){ unique_id, NULL }); + gtk_string_list_splice (self->installed_versions, 0, 0, (const char *const[]){ installed_version != NULL ? installed_version : "", NULL }); if (title != NULL) { @@ -1211,7 +1211,7 @@ installed_changed (BzEntryGroup *self, if (index != G_MAXUINT) { gtk_string_list_splice (self->installed_versions, index, 1, - (const char *const[]) { + (const char *const[]){ version != NULL ? version : "", NULL }); } diff --git a/src/bz-flatpak-entry.c b/src/bz-flatpak-entry.c index 38767140..8b465234 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, @@ -129,6 +131,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; @@ -169,6 +174,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); } @@ -221,6 +227,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", @@ -267,7 +279,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) @@ -309,8 +324,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) @@ -406,6 +425,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)); @@ -663,7 +691,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 * @@ -677,8 +706,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)); @@ -780,6 +807,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, @@ -835,5 +869,6 @@ 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..fa6010e6 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, @@ -949,11 +957,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); @@ -1022,11 +1034,15 @@ load_local_ref_fiber (LoadLocalRefData *data) 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 +1060,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 +1226,7 @@ load_local_ref_fiber (LoadLocalRefData *data) entry = bz_flatpak_entry_new_for_ref ( FLATPAK_REF (bref), - NULL, + remote, FALSE, component, NULL, @@ -1103,7 +1239,47 @@ load_local_ref_fiber (LoadLocalRefData *data) path, local_error->message); - return dex_future_new_for_object (entry); + { + g_autoptr (BzBackendNotification) notif = NULL; + g_autoptr (BzBackendNotification) inc_incoming = NULL; + g_autoptr (GMutexLocker) locker = NULL; + + notif = bz_backend_notification_new (); + bz_backend_notification_set_kind (notif, BZ_BACKEND_NOTIFICATION_KIND_REPLACE_ENTRY); + bz_backend_notification_set_entry (notif, BZ_ENTRY (entry)); + + inc_incoming = bz_backend_notification_new (); + bz_backend_notification_set_kind (inc_incoming, BZ_BACKEND_NOTIFICATION_KIND_TELL_INCOMING); + bz_backend_notification_set_n_incoming (inc_incoming, 1); + + locker = g_mutex_locker_new (&self->notif_mutex); + for (guint i = 0; i < self->notif_channels->len;) + { + DexChannel *channel = NULL; + + channel = g_ptr_array_index (self->notif_channels, i); + if (dex_channel_can_send (channel)) + { + /* We need to ensure the notification has been completely + processed before returning the ID to avoid racing */ + dex_await ( + dex_channel_send ( + channel, + dex_future_new_for_object (inc_incoming)), + NULL); + dex_await ( + dex_channel_send ( + channel, + dex_future_new_for_object (notif)), + NULL); + i++; + } + else + g_ptr_array_remove_index_fast (self->notif_channels, i); + } + } + + return dex_future_new_for_string (name); } } @@ -1247,22 +1423,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 +1443,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 +1461,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 +1641,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 +1787,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 +2069,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 +2108,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); @@ -2436,9 +2612,11 @@ transaction_operation_done (FlatpakTransaction *transaction, 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; + if (op_type != FLATPAK_TRANSACTION_OPERATION_INSTALL_BUNDLE) + 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 || diff --git a/src/bz-full-view.blp b/src/bz-full-view.blp index c6ff0da5..18051fd3 100644 --- a/src/bz-full-view.blp +++ b/src/bz-full-view.blp @@ -118,11 +118,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-window.c b/src/bz-window.c index bc2ace68..070b65a1 100644 --- a/src/bz-window.c +++ b/src/bz-window.c @@ -303,7 +303,7 @@ browse_flathub_cb (BzWindow *self, } static void -open_search_cb (BzWindow *self, +open_search_cb (BzWindow *self, BzSearchPage *widget) { adw_view_stack_set_visible_child_name (self->main_view_stack, "search"); @@ -443,7 +443,7 @@ action_show_group (GtkWidget *widget, { AdwDialog *dialog = NULL; - dialog =bz_addons_dialog_new_single (group); + dialog = bz_addons_dialog_new_single (group); adw_dialog_present (dialog, GTK_WIDGET (self)); } else @@ -458,7 +458,7 @@ action_addons_group (GtkWidget *widget, BzWindow *self = BZ_WINDOW (widget); const char *id = NULL; g_autoptr (BzEntryGroup) group = NULL; - AdwDialog *addons_dialog = NULL; + AdwDialog *addons_dialog = NULL; id = g_variant_get_string (parameter, NULL); group = bz_application_map_factory_convert_one ( @@ -929,7 +929,7 @@ bz_window_show_entry (BzWindow *self, g_return_if_fail (BZ_IS_ENTRY (entry)); group = bz_entry_group_new_for_single_entry (entry); - bz_window_show_group(self, group); + bz_window_show_group (self, group); } void From 00ce0bef401057724c76cc6e17e24baff2a6e6c3 Mon Sep 17 00:00:00 2001 From: Alexander Vanhee <160625516+AlexanderVanhee@users.noreply.github.com> Date: Fri, 17 Apr 2026 10:37:06 +0200 Subject: [PATCH 02/11] Move bundle to cache folder before install --- src/bz-flatpak-instance.c | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/src/bz-flatpak-instance.c b/src/bz-flatpak-instance.c index fa6010e6..3000ba4a 100644 --- a/src/bz-flatpak-instance.c +++ b/src/bz-flatpak-instance.c @@ -2111,9 +2111,40 @@ transaction_fiber (TransactionData *data) if (bundle_path != NULL) /* Prioritize bundle installation */ { - g_autoptr (GFile) file = NULL; + g_autoptr (GFile) file = NULL; + g_autofree char *resolved_path = NULL; - file = g_file_new_for_path (bundle_path); + if (g_str_has_prefix (bundle_path, "/run/user/") && + strstr (bundle_path, "/doc/") != NULL) + { + g_autofree char *basename = NULL; + g_autofree char *staging = NULL; + g_autoptr (GFile) src = NULL; + g_autoptr (GFile) dst = NULL; + + basename = g_path_get_basename (bundle_path); + staging = g_build_filename (g_get_user_cache_dir (), "bundle-staging", NULL); + + g_mkdir_with_parents (staging, 0755); + + resolved_path = g_build_filename (staging, basename, NULL); + src = g_file_new_for_path (bundle_path); + dst = g_file_new_for_path (resolved_path); + + if (!g_file_copy (src, dst, + G_FILE_COPY_OVERWRITE | G_FILE_COPY_NOFOLLOW_SYMLINKS, + cancellable, NULL, NULL, &local_error)) + { + dex_channel_close_send (channel); + return dex_future_new_reject ( + BZ_FLATPAK_ERROR, + BZ_FLATPAK_ERROR_TRANSACTION_FAILURE, + "Failed to stage bundle '%s': %s", + bundle_path, local_error->message); + } + } + + file = g_file_new_for_path (resolved_path != NULL ? resolved_path : bundle_path); result = flatpak_transaction_add_install_bundle ( transaction, file, From 1d7cfc59656c6d60e8fe010b026b9a5c01d138a7 Mon Sep 17 00:00:00 2001 From: Eva M Date: Fri, 17 Apr 2026 09:00:15 -0700 Subject: [PATCH 03/11] worky? --- src/bz-application.c | 46 ++++++++--- src/bz-backend-notification.txt | 3 +- src/bz-flatpak-instance.c | 135 +++++++++++++++++--------------- 3 files changed, 109 insertions(+), 75 deletions(-) diff --git a/src/bz-application.c b/src/bz-application.c index 0feba7ab..8464f2a8 100644 --- a/src/bz-application.c +++ b/src/bz-application.c @@ -1145,7 +1145,17 @@ init_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))) + /* 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); @@ -1320,6 +1330,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; @@ -1518,6 +1539,7 @@ respond_to_flatpak_fiber (RespondToFlatpakData *data) case BZ_BACKEND_NOTIFICATION_KIND_ERROR: 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: @@ -1738,7 +1760,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); @@ -1747,15 +1768,20 @@ 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) - open_generic_id (self, g_value_get_string (value)); - else - bz_show_error_for_widget (GTK_WIDGET (window), _ ("Failed to open .flatpakref"), local_error->message); + if (value == NULL) + { + GtkWindow *window = NULL; + + 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_true (); } diff --git a/src/bz-backend-notification.txt b/src/bz-backend-notification.txt index 038dc6ed..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 invalidate_remotes 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-flatpak-instance.c b/src/bz-flatpak-instance.c index 3000ba4a..540f5d6a 100644 --- a/src/bz-flatpak-instance.c +++ b/src/bz-flatpak-instance.c @@ -1029,12 +1029,23 @@ 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 (GFile) resolved_file = NULL; const char *name = NULL; const char *origin = NULL; FlatpakInstallation *add_to_installation = NULL; @@ -1051,7 +1062,46 @@ load_local_ref_fiber (LoadLocalRefData *data) "Cannot load '%s' as a flatpak bundle: URI is not a local file", uri); - bref = flatpak_bundle_ref_new (file, &local_error); +#ifdef SANDBOXED_LIBFLATPAK + { + g_autofree char *basename = NULL; + g_autofree char *module_dir = NULL; + g_autofree char *staging = NULL; + g_autofree char *dest = NULL; + g_autoptr (GSubprocess) subprocess = NULL; + + basename = g_path_get_basename (path); + 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); + + subprocess = g_subprocess_new ( + G_SUBPROCESS_FLAGS_STDERR_PIPE, + &local_error, + "flatpak-spawn", + "--host", + "cp", + path, + dest, + NULL); + result = dex_await ( + dex_subprocess_wait_check (subprocess), + &local_error); + if (!result) + return dex_future_new_reject ( + BZ_FLATPAK_ERROR, + BZ_FLATPAK_ERROR_IO_MISBEHAVIOR, + "Failed to copy bundle from %s to %s : %s", + path, dest, local_error->message); + + resolved_file = g_file_new_for_path (dest); + } +#else + resolved_file = g_object_ref (file); +#endif + + bref = flatpak_bundle_ref_new (resolved_file, &local_error); if (bref == NULL) return dex_future_new_reject ( BZ_FLATPAK_ERROR, @@ -1240,43 +1290,31 @@ load_local_ref_fiber (LoadLocalRefData *data) local_error->message); { - g_autoptr (BzBackendNotification) notif = NULL; - g_autoptr (BzBackendNotification) inc_incoming = NULL; - g_autoptr (GMutexLocker) locker = NULL; + g_autoptr (BzBackendNotification) notif = NULL; + + notif = bz_backend_notification_new (); + bz_backend_notification_set_kind (notif, BZ_BACKEND_NOTIFICATION_KIND_TELL_INCOMING); + bz_backend_notification_set_n_incoming (notif, 1); + + send_notif_all (self, notif, TRUE); + } + { + g_autoptr (BzBackendNotification) notif = NULL; notif = bz_backend_notification_new (); bz_backend_notification_set_kind (notif, BZ_BACKEND_NOTIFICATION_KIND_REPLACE_ENTRY); bz_backend_notification_set_entry (notif, BZ_ENTRY (entry)); - inc_incoming = bz_backend_notification_new (); - bz_backend_notification_set_kind (inc_incoming, BZ_BACKEND_NOTIFICATION_KIND_TELL_INCOMING); - bz_backend_notification_set_n_incoming (inc_incoming, 1); + send_notif_all (self, notif, TRUE); + } + { + g_autoptr (BzBackendNotification) notif = NULL; - locker = g_mutex_locker_new (&self->notif_mutex); - for (guint i = 0; i < self->notif_channels->len;) - { - DexChannel *channel = 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, bz_entry_get_id (BZ_ENTRY (entry))); - channel = g_ptr_array_index (self->notif_channels, i); - if (dex_channel_can_send (channel)) - { - /* We need to ensure the notification has been completely - processed before returning the ID to avoid racing */ - dex_await ( - dex_channel_send ( - channel, - dex_future_new_for_object (inc_incoming)), - NULL); - dex_await ( - dex_channel_send ( - channel, - dex_future_new_for_object (notif)), - NULL); - i++; - } - else - g_ptr_array_remove_index_fast (self->notif_channels, i); - } + send_notif_all (self, notif, TRUE); } return dex_future_new_for_string (name); @@ -2111,40 +2149,9 @@ transaction_fiber (TransactionData *data) if (bundle_path != NULL) /* Prioritize bundle installation */ { - g_autoptr (GFile) file = NULL; - g_autofree char *resolved_path = NULL; - - if (g_str_has_prefix (bundle_path, "/run/user/") && - strstr (bundle_path, "/doc/") != NULL) - { - g_autofree char *basename = NULL; - g_autofree char *staging = NULL; - g_autoptr (GFile) src = NULL; - g_autoptr (GFile) dst = NULL; - - basename = g_path_get_basename (bundle_path); - staging = g_build_filename (g_get_user_cache_dir (), "bundle-staging", NULL); - - g_mkdir_with_parents (staging, 0755); - - resolved_path = g_build_filename (staging, basename, NULL); - src = g_file_new_for_path (bundle_path); - dst = g_file_new_for_path (resolved_path); - - if (!g_file_copy (src, dst, - G_FILE_COPY_OVERWRITE | G_FILE_COPY_NOFOLLOW_SYMLINKS, - cancellable, NULL, NULL, &local_error)) - { - dex_channel_close_send (channel); - return dex_future_new_reject ( - BZ_FLATPAK_ERROR, - BZ_FLATPAK_ERROR_TRANSACTION_FAILURE, - "Failed to stage bundle '%s': %s", - bundle_path, local_error->message); - } - } + g_autoptr (GFile) file = NULL; - file = g_file_new_for_path (resolved_path != NULL ? resolved_path : bundle_path); + file = g_file_new_for_path (bundle_path); result = flatpak_transaction_add_install_bundle ( transaction, file, From ee46e6b44bd7fe1c2dcb2b34dcd405311fbc61be Mon Sep 17 00:00:00 2001 From: Eva M Date: Fri, 17 Apr 2026 09:17:58 -0700 Subject: [PATCH 04/11] revert to using `g_file_copy` --- src/bz-flatpak-instance.c | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/src/bz-flatpak-instance.c b/src/bz-flatpak-instance.c index 540f5d6a..2da46400 100644 --- a/src/bz-flatpak-instance.c +++ b/src/bz-flatpak-instance.c @@ -1064,11 +1064,10 @@ load_local_ref_fiber (LoadLocalRefData *data) #ifdef SANDBOXED_LIBFLATPAK { - g_autofree char *basename = NULL; - g_autofree char *module_dir = NULL; - g_autofree char *staging = NULL; - g_autofree char *dest = NULL; - g_autoptr (GSubprocess) subprocess = NULL; + g_autofree char *basename = NULL; + g_autofree char *module_dir = NULL; + g_autofree char *staging = NULL; + g_autofree char *dest = NULL; basename = g_path_get_basename (path); module_dir = bz_dup_module_dir (); @@ -1076,17 +1075,12 @@ load_local_ref_fiber (LoadLocalRefData *data) g_mkdir_with_parents (staging, 0755); dest = g_build_filename (staging, basename, NULL); - subprocess = g_subprocess_new ( - G_SUBPROCESS_FLAGS_STDERR_PIPE, - &local_error, - "flatpak-spawn", - "--host", - "cp", - path, - dest, - NULL); - result = dex_await ( - dex_subprocess_wait_check (subprocess), + resolved_file = g_file_new_for_path (dest); + + result = g_file_copy ( + file, resolved_file, + G_FILE_COPY_OVERWRITE | G_FILE_COPY_NOFOLLOW_SYMLINKS, + NULL, NULL, NULL, &local_error); if (!result) return dex_future_new_reject ( @@ -1094,8 +1088,6 @@ load_local_ref_fiber (LoadLocalRefData *data) BZ_FLATPAK_ERROR_IO_MISBEHAVIOR, "Failed to copy bundle from %s to %s : %s", path, dest, local_error->message); - - resolved_file = g_file_new_for_path (dest); } #else resolved_file = g_object_ref (file); From 22ba71f3257521d1408882e737ad83e90a51d4fd Mon Sep 17 00:00:00 2001 From: Eva M Date: Sun, 3 May 2026 03:32:08 -0700 Subject: [PATCH 05/11] add "reinstallable" entry prop, copy bundles to staging immediately --- src/bz-application.c | 43 +++++- src/bz-entry-group.c | 39 ++--- src/bz-entry.c | 29 ++++ src/bz-entry.h | 5 +- src/bz-flatpak-entry.c | 5 +- src/bz-flatpak-instance.c | 278 +++++++++++++++++++++--------------- src/bz-transaction-dialog.c | 6 +- 7 files changed, 258 insertions(+), 147 deletions(-) diff --git a/src/bz-application.c b/src/bz-application.c index f64d0760..45508a5c 100644 --- a/src/bz-application.c +++ b/src/bz-application.c @@ -1251,7 +1251,7 @@ enumerate_disk_entries_fiber (GWeakRef *wr) entry = g_value_dup_object (value); if (BZ_IS_FLATPAK_ENTRY (entry) && - bz_flatpak_entry_get_bundle_path (BZ_FLATPAK_ENTRY (entry))) + bz_flatpak_entry_get_bundle_path (BZ_FLATPAK_ENTRY (entry)) != NULL) /* refrain from restoring bundle entries */ continue; @@ -2037,9 +2037,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; @@ -3235,11 +3233,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-entry-group.c b/src/bz-entry-group.c index 1e939db6..8cef7411 100644 --- a/src/bz-entry-group.c +++ b/src/bz-entry-group.c @@ -1145,12 +1145,7 @@ bz_entry_group_add (BzEntryGroup *self, } else { - 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) + if (bz_entry_is_reinstallable (entry)) { self->installable++; if (!bz_entry_is_holding (entry)) @@ -1163,7 +1158,7 @@ bz_entry_group_add (BzEntryGroup *self, } } - if (!is_addon && is_searchable && !self->searchable) + if (!is_addon && is_searchable) self->searchable = TRUE; } @@ -1206,27 +1201,23 @@ 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; 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); + 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) - { - gtk_string_list_splice (self->installed_versions, index, 1, - (const char *const[]){ - version != NULL ? version : "", - NULL }); - } + 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]); @@ -1247,22 +1238,22 @@ installed_changed (BzEntryGroup *self, else { self->removable--; - if (!is_installed_ref) + if (reinstallable) self->installable++; if (!bz_entry_is_holding (entry)) { self->removable_available--; - if (!is_installed_ref) + if (reinstallable) self->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]); } 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 8b465234..064783f7 100644 --- a/src/bz-flatpak-entry.c +++ b/src/bz-flatpak-entry.c @@ -414,6 +414,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); @@ -662,7 +663,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, @@ -678,6 +680,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); diff --git a/src/bz-flatpak-instance.c b/src/bz-flatpak-instance.c index 2da46400..59449aa2 100644 --- a/src/bz-flatpak-instance.c +++ b/src/bz-flatpak-instance.c @@ -264,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, @@ -1045,7 +1059,6 @@ load_local_ref_fiber (LoadLocalRefData *data) /* This is a bundle ref */ { g_autoptr (FlatpakBundleRef) bref = NULL; - g_autoptr (GFile) resolved_file = NULL; const char *name = NULL; const char *origin = NULL; FlatpakInstallation *add_to_installation = NULL; @@ -1062,38 +1075,7 @@ load_local_ref_fiber (LoadLocalRefData *data) "Cannot load '%s' as a flatpak bundle: URI is not a local file", uri); -#ifdef SANDBOXED_LIBFLATPAK - { - g_autofree char *basename = NULL; - g_autofree char *module_dir = NULL; - g_autofree char *staging = NULL; - g_autofree char *dest = NULL; - - basename = g_path_get_basename (path); - 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); - - resolved_file = g_file_new_for_path (dest); - - result = g_file_copy ( - file, resolved_file, - G_FILE_COPY_OVERWRITE | G_FILE_COPY_NOFOLLOW_SYMLINKS, - NULL, NULL, NULL, - &local_error); - if (!result) - return dex_future_new_reject ( - BZ_FLATPAK_ERROR, - BZ_FLATPAK_ERROR_IO_MISBEHAVIOR, - "Failed to copy bundle from %s to %s : %s", - path, dest, local_error->message); - } -#else - resolved_file = g_object_ref (file); -#endif - - bref = flatpak_bundle_ref_new (resolved_file, &local_error); + bref = flatpak_bundle_ref_new (file, &local_error); if (bref == NULL) return dex_future_new_reject ( BZ_FLATPAK_ERROR, @@ -2589,24 +2571,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), @@ -2619,76 +2592,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 (); - } - - if (op_type != FLATPAK_TRANSACTION_OPERATION_INSTALL_BUNDLE) - 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); + 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); - 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); - - 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 @@ -2840,6 +2760,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-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); } From c7390884ecdf143c9022bbe45f5c1956686f0c47 Mon Sep 17 00:00:00 2001 From: Eva M Date: Sun, 3 May 2026 04:24:05 -0700 Subject: [PATCH 06/11] add BzBundleInstallDialog --- src/bazaar.gresource.xml | 13 +- src/bz-application.c | 28 ++++ src/bz-bundle-install-dialog.blp | 50 ++++++ src/bz-bundle-install-dialog.c | 260 +++++++++++++++++++++++++++++++ src/bz-bundle-install-dialog.h | 52 +++++++ src/bz-flatpak-instance.c | 30 +--- src/meson.build | 10 +- 7 files changed, 404 insertions(+), 39 deletions(-) create mode 100644 src/bz-bundle-install-dialog.blp create mode 100644 src/bz-bundle-install-dialog.c create mode 100644 src/bz-bundle-install-dialog.h 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 45508a5c..32c3c92c 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" @@ -1824,6 +1825,33 @@ open_flatpakref_fiber (OpenFlatpakrefData *data) 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)); } return dex_future_new_true (); diff --git a/src/bz-bundle-install-dialog.blp b/src/bz-bundle-install-dialog.blp new file mode 100644 index 00000000..1a1a9bc4 --- /dev/null +++ b/src/bz-bundle-install-dialog.blp @@ -0,0 +1,50 @@ +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", + ] + 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-flatpak-instance.c b/src/bz-flatpak-instance.c index 59449aa2..353bb820 100644 --- a/src/bz-flatpak-instance.c +++ b/src/bz-flatpak-instance.c @@ -1263,35 +1263,7 @@ load_local_ref_fiber (LoadLocalRefData *data) path, local_error->message); - { - g_autoptr (BzBackendNotification) notif = NULL; - - notif = bz_backend_notification_new (); - bz_backend_notification_set_kind (notif, BZ_BACKEND_NOTIFICATION_KIND_TELL_INCOMING); - bz_backend_notification_set_n_incoming (notif, 1); - - send_notif_all (self, notif, TRUE); - } - { - g_autoptr (BzBackendNotification) notif = NULL; - - notif = bz_backend_notification_new (); - bz_backend_notification_set_kind (notif, BZ_BACKEND_NOTIFICATION_KIND_REPLACE_ENTRY); - bz_backend_notification_set_entry (notif, BZ_ENTRY (entry)); - - send_notif_all (self, notif, TRUE); - } - { - 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, bz_entry_get_id (BZ_ENTRY (entry))); - - send_notif_all (self, notif, TRUE); - } - - return dex_future_new_for_string (name); + return dex_future_new_for_object (entry); } } 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', From 29cae754c265dc7e289a0bf83abd829ca2825cab Mon Sep 17 00:00:00 2001 From: Eva M Date: Sun, 3 May 2026 04:40:32 -0700 Subject: [PATCH 07/11] fix mem leak --- src/bz-install-controls.c | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) 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); } From 25c9af3cf9d4526802cd03586628c053de701754 Mon Sep 17 00:00:00 2001 From: Eva M Date: Sun, 3 May 2026 04:48:15 -0700 Subject: [PATCH 08/11] update po/POTFILES.in --- po/POTFILES.in | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) 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 From ab04f8ef4d7b9cac1b6c0a00349d37f7de9a0a70 Mon Sep 17 00:00:00 2001 From: Eva M Date: Sun, 3 May 2026 05:47:06 -0700 Subject: [PATCH 09/11] merge functionality of remote invalidation and external changes --- src/bz-application.c | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/src/bz-application.c b/src/bz-application.c index 32c3c92c..0ef457ec 100644 --- a/src/bz-application.c +++ b/src/bz-application.c @@ -1436,23 +1436,6 @@ respond_to_flatpak_fiber (RespondToFlatpakData *data) update_labels = TRUE; } break; - case BZ_BACKEND_NOTIFICATION_KIND_INVALIDATE_REMOTES: - { - g_autoptr (GListModel) repos = NULL; - - 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); - } - } - break; case BZ_BACKEND_NOTIFICATION_KIND_REMOTE_SYNC_START: { const char *remote_name = NULL; @@ -1605,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 }; @@ -1615,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), From 881c96db6d65eaf17e0810ac48b65cb69350ef9c Mon Sep 17 00:00:00 2001 From: Eva M Date: Mon, 4 May 2026 03:29:42 -0700 Subject: [PATCH 10/11] EntryGroup: fix entry installation status becoming de-synced --- src/bz-entry-group.c | 207 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 166 insertions(+), 41 deletions(-) diff --git a/src/bz-entry-group.c b/src/bz-entry-group.c index 8cef7411..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,32 +1148,58 @@ 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)) { - if (bz_entry_is_reinstallable (entry)) + 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 = TRUE; @@ -1205,6 +1248,7 @@ installed_changed (BzEntryGroup *self, const char *unique_id = NULL; const char *version = NULL; guint index = 0; + gint32 state_flags = 0; locker = g_mutex_locker_new (&self->mutex); @@ -1212,23 +1256,44 @@ installed_changed (BzEntryGroup *self, 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); - if (index != G_MAXUINT) - gtk_string_list_splice (self->installed_versions, index, 1, - (const char *const[]){ - version != NULL ? version : "", - NULL }); - + 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]); } @@ -1237,15 +1302,37 @@ installed_changed (BzEntryGroup *self, } else { - self->removable--; + if (state_flags & ENTRY_REMOVABLE) + { + self->removable--; + state_flags &= ~ENTRY_REMOVABLE; + } + if (reinstallable) - self->installable++; + { + if (!(state_flags & ENTRY_INSTALLABLE)) + { + self->installable++; + state_flags |= ENTRY_INSTALLABLE; + } + } if (!bz_entry_is_holding (entry)) { - self->removable_available--; + if (state_flags & ENTRY_REMOVABLE_AVAILABLE) + { + self->removable_available--; + state_flags &= ~ENTRY_REMOVABLE_AVAILABLE; + } + if (reinstallable) - self->installable_available++; + { + 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 (reinstallable) @@ -1256,6 +1343,7 @@ installed_changed (BzEntryGroup *self, 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; @@ -1268,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]); From b7aced9f5a15d9d015155325415d9ff785d2b3bb Mon Sep 17 00:00:00 2001 From: Eva M Date: Mon, 4 May 2026 07:31:19 -0700 Subject: [PATCH 11/11] center button --- src/bz-bundle-install-dialog.blp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/bz-bundle-install-dialog.blp b/src/bz-bundle-install-dialog.blp index 1a1a9bc4..e8ac79c7 100644 --- a/src/bz-bundle-install-dialog.blp +++ b/src/bz-bundle-install-dialog.blp @@ -39,6 +39,7 @@ template $BzBundleInstallDialog: Adw.BreakpointBin { styles [ "suggested-action", ] + halign: center; label: _("Install Bundle"); clicked => $install_cb(template); }