/* bz-application.c * * Copyright 2025 Adam Masciola * * 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 */ #define G_LOG_DOMAIN "BAZAAR::CORE" #define BAZAAR_MODULE "core" #define MAX_IDS_PER_BLOCKLIST 2048 #define CACHE_ENUM_BATCH_SIZE 64 #define MIN_STARTUP_REFRESH_INTERVAL_SECONDS (3 * 60 * 60) #include "config.h" #include #include #include "bz-application-map-factory.h" #include "bz-application.h" #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" #include "bz-entry-cache-manager.h" #include "bz-entry-group.h" #include "bz-favorites-page.h" #include "bz-flathub-state.h" #include "bz-flatpak-bundle-result.h" #include "bz-flatpak-entry.h" #include "bz-flatpak-instance.h" #include "bz-hash-table-object.h" #include "bz-inspector.h" #include "bz-internal-config.h" #include "bz-login-page.h" #include "bz-malcontent-service.h" #include "bz-metainfo-preview.h" #include "bz-newline-parser.h" #include "bz-parser.h" #include "bz-preferences-dialog.h" #include "bz-result.h" #include "bz-root-blocklist.h" #include "bz-root-curated-config.h" #include "bz-serializable.h" #include "bz-state-info.h" #include "bz-transaction-manager.h" #include "bz-update-info.h" #include "bz-window.h" #include "bz-yaml-parser.h" #include "dex-utils.h" #include "env.h" #include "error.h" #include "io.h" #include "progress-bar-designs/common.h" #include "search-index-write.h" #include "util.h" struct _BzApplication { AdwApplication parent_instance; BzApplicationMapFactory *application_factory; BzApplicationMapFactory *entry_factory; BzContentProvider *blocklists_provider; BzContentProvider *curated_provider; BzContentProvider *txt_blocklists_provider; BzEntryCacheManager *cache; BzFlathubState *flathub; BzFlathubState *tmp_flathub; BzFlatpakInstance *flatpak; BzInternalConfig *internal_config; BzMainConfig *config; BzMalcontentService *malcontent; BzNewlineParser *txt_blocklist_parser; BzSearchEngine *search_engine; BzStateInfo *state; BzTransactionManager *transactions; BzYamlParser *blocklist_parser; BzYamlParser *curated_parser; DexChannel *flatpak_notifs; DexFuture *notif_watch; DexFuture *sync; DexPromise *first_window_opened; DexPromise *ready_to_open_files; GHashTable *eol_runtimes; GHashTable *ids_to_groups; GHashTable *ignore_eol_set; GHashTable *installed_set; GHashTable *sys_name_to_addons; GHashTable *sys_ref_to_addon_group_ids; GHashTable *usr_name_to_addons; GHashTable *usr_ref_to_addon_group_ids; GListStore *groups; GListStore *installed_apps; GListStore *search_biases_backing; GNetworkMonitor *network; GPtrArray *blocklist_regexes; GPtrArray *txt_blocked_id_sets; GSettings *settings; GTimer *init_timer; GWeakRef main_window; GtkCustomFilter *appid_filter; GtkCustomFilter *group_filter; GtkFilterListModel *group_filter_model; GtkFlattenListModel *search_biases; GtkMapListModel *blocklists_to_files; GtkMapListModel *curated_configs_to_files; GtkMapListModel *txt_blocklists_to_files; GtkStringList *blocklists; GtkStringList *curated_configs; GtkStringList *txt_blocklists; gboolean flathub_remote_initialized; gboolean had_cache_on_init; gboolean no_window_refresh; gboolean holding_for_no_window_refresh; gboolean running; guint periodic_timeout_source; int n_entries_incoming; int n_remotes_syncing; }; G_DEFINE_FINAL_TYPE (BzApplication, bz_application, ADW_TYPE_APPLICATION) BZ_DEFINE_DATA ( blocklist_regex, BlocklistRegex, { int priority; GRegex *block; GRegex *allow; }, BZ_RELEASE_DATA (block, g_regex_unref); BZ_RELEASE_DATA (allow, g_regex_unref)) static DexFuture * init_fiber (BzWeakRef *wr); static DexFuture * enumerate_disk_groups_fiber (BzWeakRef *wr); static DexFuture * enumerate_disk_io_fiber (BzEntryCacheManager *cache); static DexFuture * enumerate_disk_entries_fiber (BzWeakRef *wr); static DexFuture * check_for_updates_fiber (BzWeakRef *wr); static DexFuture * cache_flathub_fiber (BzWeakRef *wr); static DexFuture * cache_groups_fiber (BzWeakRef *wr); static DexFuture * respond_to_flatpak_fiber (BzWeakRef *wr, BzBackendNotification *notif); static DexFuture * open_appstream_fiber (BzWeakRef *wr, const char *id); static DexFuture * open_flatpakref_fiber (BzWeakRef *wr, GFile *file); static DexFuture * open_metainfo_fiber (BzWeakRef *wr, GFile *file); static DexFuture * show_update_history_fiber (BzWeakRef *wr, GVariant *history); static void open_metainfo_take (BzApplication *self, GFile *file); static void open_location_take (BzApplication *self, const char *name_for_suffix_check, GFile *file); static DexFuture * backend_sync_finally (DexFuture *future, BzWeakRef *wr); static DexFuture * backend_sync_save_groups_finally (DexFuture *future, BzWeakRef *wr); static DexFuture * init_fiber_finally (DexFuture *future, BzWeakRef *wr); static DexFuture * init_sync_finally (DexFuture *future, BzWeakRef *wr); static DexFuture * flathub_update_finally (DexFuture *future, BzWeakRef *wr); static DexFuture * cache_write_back_finally (DexFuture *future, BzWeakRef *wr, GPtrArray *notify_groups, gboolean update_filters); static DexFuture * sync_finally (DexFuture *future, BzWeakRef *wr); static DexFuture * watch_backend_notifs_then_loop_cb (DexFuture *future, BzWeakRef *wr); static BzEntryGroup * ensure_group_and_add (BzApplication *self, const char *id, BzEntry *entry, BzEntry *eol_runtime, gboolean ignore_eol, gboolean installed); static void fiber_replace_entry (BzApplication *self, BzEntry *entry); static void fiber_check_for_updates (BzApplication *self); static GFile * fiber_dup_cache_file (const char *name, char **path_out, GError **error); static gboolean periodic_timeout_cb (BzApplication *self); static gboolean scheduled_timeout_cb (BzWeakRef *wr); static void network_status_changed (BzApplication *self, GParamSpec *pspec, GNetworkMonitor *network); static void disable_blocklists_changed (BzApplication *self, GParamSpec *pspec, BzStateInfo *state); static void transactions_active_changed (BzApplication *self, GParamSpec *pspec, BzTransactionManager *transactions); static void show_hide_app_setting_changed (BzApplication *self, const char *key, GSettings *settings); static gboolean window_close_request (BzApplication *self, GtkWidget *window); static void blocklists_changed (BzApplication *self, guint position, guint removed, guint added, GListModel *model); static void txt_blocklists_changed (BzApplication *self, guint position, guint removed, guint added, GListModel *model); static void init_service_struct (BzApplication *self, GtkStringList *blocklists, GtkStringList *txt_blocklists, GtkStringList *curated_configs); static GtkWindow * get_or_create_window (BzApplication *self); static GtkWindow * new_window (BzApplication *self); static void open_appstream_take (BzApplication *self, char *appstream); static void open_flatpakref_take (BzApplication *self, GFile *file); static void command_line_open_location (BzApplication *self, GApplicationCommandLine *cmdline, const char *path); static void open_generic_id (BzApplication *self, const char *generic_id, gboolean animate); static gpointer map_strings_to_files (GtkStringObject *string, gpointer data); static gpointer map_generic_ids_to_groups (GtkStringObject *string, BzApplication *self); static gpointer map_ids_to_entries (GtkStringObject *string, BzApplication *self); static gboolean filter_application_ids (GtkStringObject *string, BzApplication *self); static gboolean filter_entry_groups (BzEntryGroup *group, BzApplication *self); static gint cmp_group (BzEntryGroup *a, BzEntryGroup *b, gpointer user_data); static gboolean validate_group_for_ui (BzApplication *self, BzEntryGroup *group); static DexFuture * make_sync_future (BzApplication *self); static void finish_with_background_task_label (BzApplication *self); static void state_info_set_icon_themes (BzStateInfo *state); static void bz_application_dispose (GObject *object) { BzApplication *self = BZ_APPLICATION (object); dex_clear (&self->first_window_opened); dex_clear (&self->flatpak_notifs); dex_clear (&self->notif_watch); dex_clear (&self->ready_to_open_files); dex_clear (&self->sync); g_clear_handle_id (&self->periodic_timeout_source, g_source_remove); g_clear_object (&self->appid_filter); g_clear_object (&self->application_factory); g_clear_object (&self->blocklist_parser); g_clear_object (&self->blocklists); g_clear_object (&self->blocklists_provider); g_clear_object (&self->blocklists_to_files); g_clear_object (&self->cache); g_clear_object (&self->curated_configs); g_clear_object (&self->curated_configs_to_files); g_clear_object (&self->curated_parser); g_clear_object (&self->curated_provider); g_clear_object (&self->entry_factory); g_clear_object (&self->flathub); g_clear_object (&self->flatpak); g_clear_object (&self->group_filter); g_clear_object (&self->group_filter_model); g_clear_object (&self->groups); g_clear_object (&self->installed_apps); g_clear_object (&self->malcontent); g_clear_object (&self->internal_config); g_clear_object (&self->network); g_clear_object (&self->search_biases); g_clear_object (&self->search_biases_backing); g_clear_object (&self->search_engine); g_clear_object (&self->settings); g_clear_object (&self->state); g_clear_object (&self->tmp_flathub); g_clear_object (&self->transactions); g_clear_object (&self->txt_blocklist_parser); g_clear_object (&self->txt_blocklists); g_clear_object (&self->txt_blocklists_provider); g_clear_object (&self->txt_blocklists_to_files); g_clear_pointer (&self->blocklist_regexes, g_ptr_array_unref); g_clear_pointer (&self->eol_runtimes, g_hash_table_unref); g_clear_pointer (&self->ids_to_groups, g_hash_table_unref); g_clear_pointer (&self->ignore_eol_set, g_hash_table_unref); g_clear_pointer (&self->init_timer, g_timer_destroy); g_clear_pointer (&self->installed_set, g_hash_table_unref); g_clear_pointer (&self->sys_name_to_addons, g_hash_table_unref); g_clear_pointer (&self->txt_blocked_id_sets, g_ptr_array_unref); g_clear_pointer (&self->usr_name_to_addons, g_hash_table_unref); g_clear_pointer (&self->sys_ref_to_addon_group_ids, g_hash_table_unref); g_clear_pointer (&self->usr_ref_to_addon_group_ids, g_hash_table_unref); g_weak_ref_clear (&self->main_window); G_OBJECT_CLASS (bz_application_parent_class)->dispose (object); } static void bz_application_activate (GApplication *app) { BzApplication *self = BZ_APPLICATION (app); new_window (self); } static int bz_application_command_line (GApplication *app, GApplicationCommandLine *cmdline) { BzApplication *self = BZ_APPLICATION (app); g_autoptr (GError) local_error = NULL; gint argc = 0; g_auto (GStrv) argv = NULL; gboolean help = FALSE; gboolean no_window = FALSE; gboolean no_window_refresh = FALSE; g_auto (GStrv) blocklists_strv = NULL; g_auto (GStrv) content_configs_strv = NULL; g_auto (GStrv) locations = NULL; g_autofree char *search_term = NULL; GOptionEntry main_entries[] = { { "help", 0, 0, G_OPTION_ARG_NONE, &help, "Print help" }, { "no-window", 0, 0, G_OPTION_ARG_NONE, &no_window, "Ensure the service is running without creating a new window (daemon)" }, { "no-window-refresh", 0, 0, G_OPTION_ARG_NONE, &no_window_refresh, "Refresh remotes in the background, and quits if no windows are open at the end." }, { "extra-blocklist", 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &blocklists_strv, "Add an extra blocklist to read from" }, { "extra-curated-config", 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &content_configs_strv, "Add an extra yaml file with which to configure the app browser" }, /* Here for backwards compat */ { "extra-content-config", 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &content_configs_strv, "Add an extra yaml file with which to configure the app browser (backwards compat)" }, { "search-for", 0, 0, G_OPTION_ARG_STRING, &search_term, "Open search with this term" }, { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &locations, "flatpakref file to open" }, { NULL } }; argv = g_application_command_line_get_arguments (cmdline, &argc); g_debug ("Handling gapplication command line; argc=%d, argv= \\", argc); for (guint i = 0; i < argc; i++) { g_debug (" [%d] %s", i, argv[i]); } if (argv != NULL && argc > 0) { g_autofree GStrv argv_shallow = NULL; g_autoptr (GOptionContext) context = NULL; argv_shallow = g_memdup2 (argv, sizeof (*argv) * argc); context = g_option_context_new ("- an app center for GNOME"); g_option_context_set_help_enabled (context, FALSE); g_option_context_add_main_entries (context, main_entries, NULL); if (!g_option_context_parse (context, &argc, &argv_shallow, &local_error)) { g_application_command_line_printerr (cmdline, "%s\n", local_error->message); return EXIT_FAILURE; } if (help) { g_autofree char *help_text = NULL; if (self->running) g_application_command_line_printerr (cmdline, "The Bazaar service is running.\n\n"); else g_application_command_line_printerr (cmdline, "The Bazaar service is not running.\n\n"); help_text = g_option_context_get_help (context, TRUE, NULL); g_application_command_line_printerr (cmdline, "%s\n", help_text); return EXIT_SUCCESS; } if (no_window) g_application_command_line_printerr (cmdline, "--no-window only works with bazaar-daemon\n"); } if (!self->running) { g_autoptr (GtkStringList) blocklists = NULL; g_autoptr (GtkStringList) txt_blocklists = NULL; g_autoptr (GtkStringList) content_configs = NULL; g_autoptr (DexFuture) init = NULL; self->running = TRUE; self->no_window_refresh = no_window_refresh; if (self->no_window_refresh) { self->holding_for_no_window_refresh = TRUE; g_application_hold (G_APPLICATION (self)); } blocklists = gtk_string_list_new (NULL); txt_blocklists = gtk_string_list_new (NULL); content_configs = gtk_string_list_new (NULL); init_service_struct (self, blocklists, txt_blocklists, content_configs); #ifdef HARDCODED_BLOCKLIST g_debug ("Bazaar was configured with a hardcoded txt blocklist at %s, adding that now...", HARDCODED_BLOCKLIST); gtk_string_list_append (txt_blocklists, HARDCODED_BLOCKLIST); #endif if (blocklists_strv != NULL) gtk_string_list_splice ( txt_blocklists, g_list_model_get_n_items (G_LIST_MODEL (txt_blocklists)), 0, (const char *const *) blocklists_strv); #ifdef HARDCODED_CONTENT_CONFIG g_debug ("Bazaar was configured with a hardcoded curated content config at %s, adding that now...", HARDCODED_CONTENT_CONFIG); gtk_string_list_append (content_configs, HARDCODED_CONTENT_CONFIG); #endif if (content_configs_strv != NULL) gtk_string_list_splice ( content_configs, g_list_model_get_n_items (G_LIST_MODEL (content_configs)), 0, (const char *const *) content_configs_strv); g_timer_start (self->init_timer); init = dex_scheduler_spawn ( dex_scheduler_get_default (), bz_get_dex_stack_size (), (DexFiberFunc) init_fiber, bz_weak_ref_new (self), (GDestroyNotify) bz_weak_ref_unref); init = dex_future_finally ( init, (DexFutureCallback) init_fiber_finally, bz_weak_ref_new (self), (GDestroyNotify) bz_weak_ref_unref); dex_future_disown (g_steal_pointer (&init)); } if ((locations == NULL || *locations == NULL) && search_term == NULL && !no_window_refresh) new_window (self); else if (!no_window_refresh) get_or_create_window (self); if (locations != NULL && *locations != NULL) command_line_open_location (self, cmdline, locations[0]); if (search_term != NULL) bz_window_search (BZ_WINDOW (get_or_create_window (self)), search_term); return EXIT_SUCCESS; } static gboolean bz_application_local_command_line (GApplication *application, gchar ***arguments, int *exit_status) { return FALSE; } static void bz_application_class_init (BzApplicationClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GApplicationClass *app_class = G_APPLICATION_CLASS (klass); object_class->dispose = bz_application_dispose; app_class->activate = bz_application_activate; app_class->command_line = bz_application_command_line; app_class->local_command_line = bz_application_local_command_line; g_type_ensure (BZ_TYPE_RESULT); } static void bz_application_toggle_debug_mode_action (GSimpleAction *action, GVariant *parameter, gpointer user_data) { BzApplication *self = user_data; gboolean debug_mode = FALSE; debug_mode = bz_state_info_get_debug_mode (self->state); bz_state_info_set_debug_mode (self->state, !debug_mode); } static void bz_application_bazaar_inspector_action (GSimpleAction *action, GVariant *parameter, gpointer user_data) { BzApplication *self = user_data; BzInspector *inspector = NULL; g_assert (BZ_IS_APPLICATION (self)); inspector = bz_inspector_new (); bz_inspector_set_state (inspector, self->state); gtk_application_add_window (GTK_APPLICATION (self), GTK_WINDOW (inspector)); gtk_window_present (GTK_WINDOW (inspector)); } static void bz_application_donate_action (GSimpleAction *action, GVariant *parameter, gpointer user_data) { BzApplication *self = user_data; GtkWindow *window = NULL; AdwDialog *dialog = NULL; g_assert (BZ_IS_APPLICATION (self)); window = get_or_create_window (self); dialog = bz_donations_dialog_new (); adw_dialog_present (dialog, GTK_WIDGET (window)); bz_state_info_set_donation_prompt_dismissed (self->state, TRUE); } static void bz_application_search_action (GSimpleAction *action, GVariant *parameter, gpointer user_data) { BzApplication *self = user_data; GtkWindow *window = NULL; const char *initial_text = NULL; g_assert (BZ_IS_APPLICATION (self)); window = get_or_create_window (self); if (parameter != NULL) initial_text = g_variant_get_string (parameter, NULL); bz_window_search (BZ_WINDOW (window), initial_text); } static void bz_application_show_app_id_action (GSimpleAction *action, GVariant *parameter, gpointer user_data) { BzApplication *self = user_data; GtkWindow *window = NULL; g_assert (BZ_IS_APPLICATION (self)); window = get_or_create_window (self); if (parameter != NULL) gtk_widget_activate_action (GTK_WIDGET (window), "window.show-group", "(sb)", g_variant_get_string (parameter, NULL), TRUE); } static void bz_application_open_location_action (GSimpleAction *action, GVariant *parameter, gpointer user_data) { BzApplication *self = user_data; const char *uri = NULL; g_autoptr (GFile) file = NULL; g_autofree char *basename = NULL; g_assert (BZ_IS_APPLICATION (self)); uri = g_variant_get_string (parameter, NULL); if (uri == NULL || *uri == '\0') return; file = g_file_new_for_uri (uri); basename = g_file_get_basename (file); open_location_take (self, basename, g_object_ref (file)); } static void bz_application_sync_remotes_action (GSimpleAction *action, GVariant *parameter, gpointer user_data) { BzApplication *self = user_data; g_assert (BZ_IS_APPLICATION (self)); if (self->sync != NULL && dex_future_is_pending (self->sync)) return; dex_clear (&self->sync); self->sync = make_sync_future (self); } static void bz_application_about_action (GSimpleAction *action, GVariant *parameter, gpointer user_data) { BzApplication *self = user_data; GtkWindow *window = NULL; AdwDialog *dialog = NULL; const char *developers[] = { "Adam Masciola ", "Alexander Vanhee", NULL }; const char *special_thanks[] = { "arewelibadwaitayet https://arewelibadwaitayet.com/", /* This array MUST be NULL terminated */ NULL }; g_assert (BZ_IS_APPLICATION (self)); window = gtk_application_get_active_window (GTK_APPLICATION (self)); dialog = adw_about_dialog_new (); g_object_set ( dialog, "application-name", "Bazaar", "application-icon", "io.github.kolunmi.Bazaar", "developer-name", _ ("The Bazaar Contributors"), "developers", developers, // Translators: Put one translator per line, in the form NAME , YEAR1, YEAR2 "translator-credits", _ ("translator-credits"), "version", PACKAGE_VCS_VERSION, "copyright", "© 2025-2026 The Bazaar Contributors", "license-type", GTK_LICENSE_GPL_3_0, "website", "https://usebazaar.org", "issue-url", "https://gitlab.gnome.org/World/bazaar/-/work_items", NULL); adw_about_dialog_add_acknowledgement_section ( ADW_ABOUT_DIALOG (dialog), _ ("Special Thanks"), special_thanks); adw_dialog_present (dialog, GTK_WIDGET (window)); } static void bz_application_preferences_action (GSimpleAction *action, GVariant *parameter, gpointer user_data) { BzApplication *self = user_data; GtkWindow *window = NULL; AdwDialog *preferences = NULL; g_assert (BZ_IS_APPLICATION (self)); window = gtk_application_get_active_window (GTK_APPLICATION (self)); preferences = bz_preferences_dialog_new (self->state); adw_dialog_present (preferences, GTK_WIDGET (window)); } static void bz_application_flathub_login_action (GSimpleAction *action, GVariant *parameter, gpointer user_data) { BzApplication *self = user_data; GtkWindow *window = NULL; BzAuthState *auth_state = NULL; AdwNavigationPage *login_page = NULL; g_assert (BZ_IS_APPLICATION (self)); window = gtk_application_get_active_window (GTK_APPLICATION (self)); auth_state = bz_state_info_get_auth_state (self->state); login_page = bz_login_page_new (auth_state); bz_window_push_page (BZ_WINDOW (window), login_page); } static void bz_application_flathub_logout_action (GSimpleAction *action, GVariant *parameter, gpointer user_data) { BzApplication *self = BZ_APPLICATION (user_data); GtkWindow *window = gtk_application_get_active_window (GTK_APPLICATION (self)); BzAuthState *auth_state = bz_state_info_get_auth_state (self->state); g_assert (BZ_IS_WINDOW (window)); bz_auth_state_clear (auth_state); bz_window_add_toast ( BZ_WINDOW (window), adw_toast_new (_ ("Logged Out Successfully!"))); } static void bz_application_flathub_favorites_action (GSimpleAction *action, GVariant *parameter, gpointer user_data) { BzApplication *self = user_data; GtkWindow *window = NULL; AdwNavigationPage *favorites_page = NULL; g_assert (BZ_IS_APPLICATION (self)); window = get_or_create_window (self); favorites_page = ADW_NAVIGATION_PAGE (bz_favorites_page_new (self->state)); bz_window_push_page (BZ_WINDOW (window), favorites_page); } static void bz_application_show_update_history_action (GSimpleAction *action, GVariant *parameter, gpointer user_data) { BzApplication *self = user_data; g_autoptr (BzWeakRef) wr = NULL; g_assert (BZ_IS_APPLICATION (self)); if (parameter == NULL) return; wr = bz_weak_ref_new (self); dex_future_disown (dex_scheduler_spawnv ( dex_scheduler_get_default (), bz_get_dex_stack_size (), G_CALLBACK (show_update_history_fiber), 2, BZ_TYPE_WEAK_REF, wr, G_TYPE_VARIANT, parameter)); } static void bz_application_quit_action (GSimpleAction *action, GVariant *parameter, gpointer user_data) { BzApplication *self = user_data; g_autoptr (GDBusConnection) conn = NULL; g_autoptr (GError) error = NULL; conn = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); if (conn != NULL) g_dbus_connection_call_sync ( conn, "io.github.kolunmi.Bazaar.SearchProvider", "/io/github/kolunmi/Bazaar/SearchProvider", "io.github.kolunmi.Bazaar.Daemon", "Quit", NULL, NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error); if (error != NULL) g_warning ("Failed to quit daemon: %s", error->message); g_application_quit (G_APPLICATION (self)); } static const GActionEntry app_actions[] = { { "flathub-login", bz_application_flathub_login_action, NULL }, { "flathub-logout", bz_application_flathub_logout_action, NULL }, { "flathub-favorites", bz_application_flathub_favorites_action, NULL }, { "show-update-history", bz_application_show_update_history_action, "a(sss)" }, { "quit", bz_application_quit_action, NULL }, { "preferences", bz_application_preferences_action, NULL }, { "about", bz_application_about_action, NULL }, { "sync-remotes", bz_application_sync_remotes_action, NULL }, { "search", bz_application_search_action, "s" }, { "show-app-id", bz_application_show_app_id_action, "s" }, { "donate", bz_application_donate_action, NULL }, { "bazaar-inspector", bz_application_bazaar_inspector_action, NULL }, { "toggle-debug-mode", bz_application_toggle_debug_mode_action, NULL }, { "open-location", bz_application_open_location_action, "s" }, }; static void bz_application_init (BzApplication *self) { self->running = FALSE; g_weak_ref_init (&self->main_window, NULL); g_action_map_add_action_entries ( G_ACTION_MAP (self), app_actions, G_N_ELEMENTS (app_actions), self); gtk_application_set_accels_for_action ( GTK_APPLICATION (self), "app.quit", (const char *[]){ "q", NULL }); gtk_application_set_accels_for_action ( GTK_APPLICATION (self), "app.preferences", (const char *[]){ "comma", NULL }); gtk_application_set_accels_for_action ( GTK_APPLICATION (self), "app.sync-remotes", (const char *[]){ "r", NULL }); gtk_application_set_accels_for_action ( GTK_APPLICATION (self), "app.search('')", (const char *[]){ "f", NULL }); gtk_application_set_accels_for_action ( GTK_APPLICATION (self), "app.bazaar-inspector", (const char *[]){ "i", NULL }); gtk_application_set_accels_for_action ( GTK_APPLICATION (self), "app.toggle-debug-mode", (const char *[]){ "d", NULL }); } BzStateInfo * bz_state_info_get_default (void) { GApplication *app = NULL; BzApplication *self = NULL; app = g_application_get_default (); if G_UNLIKELY (app == NULL) { static BzStateInfo *fallback_state = NULL; if (g_once_init_enter_pointer (&fallback_state)) { g_autoptr (BzStateInfo) tmp = NULL; tmp = bz_state_info_new (); // state_info_set_icon_themes (tmp); g_once_init_leave_pointer ( &fallback_state, g_steal_pointer (&tmp)); } return fallback_state; } self = (BzApplication *) app; g_assert (BZ_IS_APPLICATION (self)); return self->state; } static DexFuture * init_fiber (BzWeakRef *wr) { g_autoptr (BzApplication) self = NULL; g_autoptr (GError) local_error = NULL; g_autofree char *root_cache_dir = NULL; g_autoptr (GFile) root_cache_dir_file = NULL; g_autoptr (GListModel) repos = NULL; gboolean has_flathub = FALSE; gboolean cache_has_flathub = FALSE; gboolean result = FALSE; g_autoptr (BzAuthState) auth_state = NULL; g_autofree char *flathub_cache = NULL; g_autoptr (GFile) flathub_cache_file = NULL; g_autofree char *cache_version_path = NULL; g_autoptr (GFile) cache_version_file = NULL; bz_weak_get_or_return_reject (self, &wr->ref); /* Here until Ubuntu fixes its apparmor crap. */ #ifdef SANDBOXED_LIBFLATPAK if (g_settings_get_boolean (self->settings, "check-for-ubuntu")) { g_autoptr (GSubprocessLauncher) os_check_launcher = NULL; g_autoptr (GSubprocess) os_check = NULL; g_autoptr (GError) os_check_error = NULL; gboolean os_check_result = FALSE; GInputStream *os_check_stdout_pipe = NULL; g_autoptr (GBytes) os_out = NULL; gboolean affected_ubuntu = FALSE; os_check_launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDOUT_PIPE); os_check = g_subprocess_launcher_spawn (os_check_launcher, &os_check_error, "flatpak-spawn", "--host", "cat", "/usr/lib/os-release", NULL); if (os_check != NULL) { os_check_result = dex_await (dex_subprocess_wait_check (os_check), &os_check_error); if (os_check_result) { os_check_stdout_pipe = g_subprocess_get_stdout_pipe (os_check); os_out = g_input_stream_read_bytes (os_check_stdout_pipe, 4096, NULL, &os_check_error); } else g_warning ("Ubuntu check subprocess failed: %s", os_check_error->message); } else g_warning ("Failed to spawn ubuntu check subprocess: %s", os_check_error->message); if (os_out != NULL) { const char *data = NULL; const char *ver = NULL; int major = 0; int minor = 0; data = g_bytes_get_data (os_out, NULL); ver = strstr (data, "VERSION_ID=\""); if (strstr (data, "ID=ubuntu") != NULL && ver != NULL && sscanf (ver + strlen ("VERSION_ID=\""), "%d.%d", &major, &minor) == 2) affected_ubuntu = major > 25 || (major == 25 && minor >= 10); } if (affected_ubuntu) { GtkWindow *window = NULL; AdwDialog *alert = NULL; g_autofree char *response = NULL; dex_await (dex_ref (DEX_FUTURE (self->first_window_opened)), NULL); window = gtk_application_get_active_window (GTK_APPLICATION (self)); if (window == NULL) window = new_window (self); alert = adw_alert_dialog_new (NULL, NULL); adw_alert_dialog_format_heading (ADW_ALERT_DIALOG (alert), _ ("Notice for Ubuntu Users")); adw_alert_dialog_format_body_markup ( ADW_ALERT_DIALOG (alert), _ ("Ubuntu 25.10 and newer have changed default security rules in " "a way that makes it impossible to install apps from " "the Flatpak version of Bazaar. " "For the time being, please use the apt package instead. " "Install it with\n\n" "sudo apt install bazaar\n\n" "You can then remove this Flatpak version with\n\n" "flatpak uninstall io.github.kolunmi.Bazaar")); adw_alert_dialog_add_response (ADW_ALERT_DIALOG (alert), "ok", _ ("Continue Anyway")); adw_alert_dialog_set_default_response (ADW_ALERT_DIALOG (alert), "ok"); adw_alert_dialog_set_close_response (ADW_ALERT_DIALOG (alert), "ok"); adw_dialog_present (alert, GTK_WIDGET (window)); response = dex_await_string (bz_make_alert_dialog_future (ADW_ALERT_DIALOG (alert)), NULL); g_settings_set_boolean (self->settings, "check-for-ubuntu", FALSE); } else g_settings_set_boolean (self->settings, "check-for-ubuntu", FALSE); } #endif bz_state_info_set_online (self->state, TRUE); bz_state_info_set_busy (self->state, TRUE); bz_state_info_set_background_task_label (self->state, _ ("Performing setup…")); root_cache_dir = bz_dup_root_cache_dir (); root_cache_dir_file = g_file_new_for_path (root_cache_dir); cache_version_path = g_build_filename (root_cache_dir, "cache-version", NULL); cache_version_file = g_file_new_for_path (cache_version_path); if (dex_await (dex_file_query_exists (root_cache_dir_file), NULL)) { gboolean wipe_cache = TRUE; gboolean package_upgraded = TRUE; if (dex_await (dex_file_query_exists (cache_version_file), NULL)) { g_autoptr (GBytes) bytes = NULL; bytes = dex_await_boxed (dex_file_load_contents_bytes (cache_version_file), NULL); if (bytes != NULL) { g_autoptr (GVariant) variant = NULL; variant = g_variant_new_from_bytes (G_VARIANT_TYPE ("(ss)"), bytes, FALSE); if (variant != NULL) { const char *stored_package_version = NULL; const char *stored_cache_version = NULL; g_variant_get (variant, "(&s&s)", &stored_package_version, &stored_cache_version); wipe_cache = g_strcmp0 (stored_cache_version, CACHE_VERSION) != 0; package_upgraded = g_strcmp0 (stored_package_version, PACKAGE_VERSION) != 0; } } } if (package_upgraded) bz_state_info_set_donation_prompt_dismissed (self->state, FALSE); if (wipe_cache) { g_settings_set_int64 (self->settings, "last-refresh-time", 0); g_info ("Version incompatibility detected: clearing cache"); dex_await (bz_reap_file_dex (root_cache_dir_file), NULL); } } else bz_state_info_set_donation_prompt_dismissed (self->state, TRUE); { g_autoptr (GError) mkdir_error = NULL; dex_await (dex_file_make_directory_with_parents (root_cache_dir_file), &mkdir_error); if (mkdir_error == NULL || mkdir_error->code == G_IO_ERROR_EXISTS) { g_autoptr (GVariant) variant = NULL; g_autoptr (GBytes) bytes = NULL; variant = g_variant_new ("(ss)", PACKAGE_VERSION, CACHE_VERSION); bytes = g_variant_get_data_as_bytes (variant); dex_await (dex_file_replace_contents_bytes ( cache_version_file, bytes, NULL, FALSE, G_FILE_CREATE_REPLACE_DESTINATION), NULL); } else g_warning ("Unable to ensure cache directory: %s", mkdir_error->message); } g_clear_object (&self->flatpak); self->flatpak = dex_await_object (bz_flatpak_instance_new (), &local_error); if (self->flatpak == NULL) return dex_future_new_for_error (g_steal_pointer (&local_error)); bz_transaction_manager_set_backend (self->transactions, BZ_BACKEND (self->flatpak)); bz_state_info_set_backend (self->state, BZ_BACKEND (self->flatpak)); has_flathub = dex_await_boolean ( bz_flatpak_instance_has_flathub (self->flatpak, NULL), &local_error); if (local_error != NULL) return dex_future_new_for_error (g_steal_pointer (&local_error)); if (!has_flathub) { GtkWindow *window = NULL; g_autofree char *response = NULL; AdwDialog *alert = NULL; dex_await (dex_ref (DEX_FUTURE (self->first_window_opened)), NULL); window = gtk_application_get_active_window (GTK_APPLICATION (self)); if (window == NULL) window = new_window (self); alert = adw_alert_dialog_new (NULL, NULL); #ifdef SANDBOXED_LIBFLATPAK adw_alert_dialog_format_heading ( ADW_ALERT_DIALOG (alert), _ ("Set Up System Flathub?")); adw_alert_dialog_format_body ( ADW_ALERT_DIALOG (alert), _ ("The system Flathub remote is not set up. Bazaar requires " "Flathub to be configured on the system Flatpak installation " "to browse and install applications.\n\n" "You can still use Bazaar to browse and remove already installed apps.")); #else adw_alert_dialog_format_heading ( ADW_ALERT_DIALOG (alert), _ ("Set Up Flathub?")); adw_alert_dialog_format_body ( ADW_ALERT_DIALOG (alert), _ ("Flathub is not set up on this system. " "You will not be able to browse and install applications in Bazaar if its unavailable.\n\n" "You can still use Bazaar to browse and remove already installed apps.")); #endif adw_alert_dialog_add_responses ( ADW_ALERT_DIALOG (alert), "later", _ ("Later"), "add", _ ("Set Up Flathub"), NULL); adw_alert_dialog_set_response_appearance ( ADW_ALERT_DIALOG (alert), "add", ADW_RESPONSE_SUGGESTED); adw_alert_dialog_set_default_response (ADW_ALERT_DIALOG (alert), "add"); adw_alert_dialog_set_close_response (ADW_ALERT_DIALOG (alert), "later"); adw_dialog_present (alert, GTK_WIDGET (window)); response = dex_await_string ( bz_make_alert_dialog_future (ADW_ALERT_DIALOG (alert)), NULL); if (response != NULL && g_strcmp0 (response, "add") == 0) { result = dex_await ( bz_flatpak_instance_ensure_has_flathub (self->flatpak, NULL), &local_error); if (result) has_flathub = TRUE; else { g_warning ("Failed to install flathub: %s", local_error->message); g_clear_error (&local_error); } } } bz_state_info_set_has_flathub (self->state, has_flathub); self->installed_set = dex_await_boxed ( bz_backend_retrieve_install_ids ( BZ_BACKEND (self->flatpak), NULL), &local_error); if (self->installed_set == NULL) { g_warning ("Unable to enumerate installed entries from flatpak backend; " "no entries will appear to be installed: %s", local_error->message); g_clear_error (&local_error); self->installed_set = g_hash_table_new_full ( g_str_hash, g_str_equal, g_free, g_free); } 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); } /* Revive old cache from previous Bazaar process */ cache_has_flathub = dex_await_boolean ( dex_scheduler_spawn ( dex_scheduler_get_default (), bz_get_dex_stack_size (), (DexFiberFunc) enumerate_disk_groups_fiber, bz_weak_ref_ref (wr), (GDestroyNotify) bz_weak_ref_unref), NULL); self->had_cache_on_init = g_list_model_get_n_items (G_LIST_MODEL (self->groups)) > 0; flathub_cache_file = fiber_dup_cache_file ("flathub-cache", &flathub_cache, &local_error); if (flathub_cache_file != NULL) { if (dex_await (dex_file_query_exists (flathub_cache_file), NULL)) { g_autoptr (GBytes) bytes = NULL; bytes = dex_await_boxed ( dex_file_load_contents_bytes (flathub_cache_file), &local_error); if (bytes != NULL) { g_autoptr (GVariant) variant = NULL; g_autoptr (BzFlathubState) flathub = NULL; variant = g_variant_new_from_bytes (G_VARIANT_TYPE_VARDICT, bytes, FALSE); if (variant == NULL) { g_warning ("Failed to interpret cached flathub state from %s: %s", flathub_cache, local_error->message); g_clear_error (&local_error); } flathub = bz_flathub_state_new (); result = bz_serializable_deserialize ( BZ_SERIALIZABLE (flathub), variant, &local_error); if (result) { self->flathub = g_steal_pointer (&flathub); bz_flathub_state_set_map_factory (self->flathub, self->application_factory); bz_state_info_set_flathub (self->state, self->flathub); if (cache_has_flathub) { dex_promise_resolve_boolean (self->ready_to_open_files, TRUE); bz_state_info_set_busy (self->state, FALSE); } } else { g_warning ("Failed to deserialize cached flathub state from %s: %s", flathub_cache, local_error->message); g_clear_error (&local_error); } } else { g_warning ("Failed to decache cache flathub state from %s: %s", flathub_cache, local_error->message); g_clear_error (&local_error); } } } else { g_warning ("Unable to ensure cache directory: %s", local_error->message); g_clear_error (&local_error); } auth_state = bz_auth_state_new (); bz_state_info_set_auth_state (self->state, auth_state); return dex_future_new_true (); } static DexFuture * enumerate_disk_groups_fiber (BzWeakRef *wr) { g_autoptr (BzApplication) self = NULL; g_autoptr (GError) local_error = NULL; g_autofree char *groups_cache = NULL; g_autoptr (GFile) groups_cache_file = NULL; g_autoptr (GBytes) bytes = NULL; g_autoptr (GVariant) variant = NULL; g_autoptr (GVariantIter) iter = NULL; gboolean has_flathub_group = FALSE; bz_weak_get_or_return_reject (self, &wr->ref); groups_cache_file = fiber_dup_cache_file ("groups-cache", &groups_cache, &local_error); if (groups_cache_file == NULL) { g_warning ("Unable to ensure groups cache directory: %s", local_error->message); return dex_future_new_for_boolean (FALSE); } if (!dex_await (dex_file_query_exists (groups_cache_file), NULL)) return dex_future_new_for_boolean (FALSE); bytes = dex_await_boxed ( dex_file_load_contents_bytes (groups_cache_file), &local_error); if (bytes == NULL) { g_warning ("Failed to load groups cache from %s: %s", groups_cache, local_error->message); return dex_future_new_for_boolean (FALSE); } variant = g_variant_new_from_bytes (G_VARIANT_TYPE ("aa{sv}"), bytes, FALSE); if (variant == NULL) { g_warning ("Failed to parse groups cache from %s", groups_cache); return dex_future_new_for_boolean (FALSE); } iter = g_variant_iter_new (variant); for (;;) { g_autoptr (GVariant) group_variant = NULL; g_autoptr (BzEntryGroup) group = NULL; group_variant = g_variant_iter_next_value (iter); if (group_variant == NULL) break; group = bz_entry_group_new (self->entry_factory); if (bz_entry_group_deserialize (group, group_variant)) { const char *id = NULL; gboolean is_installed = FALSE; is_installed = bz_entry_group_reconcile_with_installed_set (group, self->installed_set); if (!has_flathub_group && bz_entry_group_get_is_flathub (group)) has_flathub_group = TRUE; g_list_store_append (self->groups, group); id = bz_entry_group_get_id (group); if (id != NULL) g_hash_table_replace ( self->ids_to_groups, g_strdup (id), g_object_ref (group)); if (is_installed) g_list_store_insert_sorted ( self->installed_apps, group, (GCompareDataFunc) cmp_group, NULL); } } gtk_filter_changed (GTK_FILTER (self->group_filter), GTK_FILTER_CHANGE_LESS_STRICT); gtk_filter_changed (GTK_FILTER (self->appid_filter), GTK_FILTER_CHANGE_LESS_STRICT); dex_future_disown (dex_scheduler_spawn ( dex_scheduler_get_default (), bz_get_dex_stack_size (), (DexFiberFunc) check_for_updates_fiber, bz_weak_ref_ref (wr), (GDestroyNotify) bz_weak_ref_unref)); return dex_future_new_for_boolean (has_flathub_group); } static DexFuture * enumerate_disk_io_fiber (BzEntryCacheManager *cache) { g_autoptr (GError) local_error = NULL; g_autoptr (GHashTable) cached_set = NULL; g_autoptr (GPtrArray) entries = NULL; GHashTableIter iter = { 0 }; cached_set = dex_await_boxed ( bz_entry_cache_manager_enumerate_disk (cache), &local_error); if (cached_set == NULL) return dex_future_new_for_error (g_steal_pointer (&local_error)); entries = g_ptr_array_new_with_free_func (g_object_unref); g_hash_table_iter_init (&iter, cached_set); for (;;) { g_autoptr (GPtrArray) batch = NULL; char *checksum = NULL; batch = g_ptr_array_new_with_free_func (dex_unref); while (batch->len < CACHE_ENUM_BATCH_SIZE && g_hash_table_iter_next (&iter, (gpointer *) &checksum, NULL)) g_ptr_array_add ( batch, bz_entry_cache_manager_get_by_checksum ( cache, checksum)); if (batch->len > 0) dex_await (dex_future_allv ( (DexFuture *const *) batch->pdata, batch->len), NULL); for (guint i = 0; i < batch->len; i++) { DexFuture *future = NULL; const GValue *value = NULL; future = g_ptr_array_index (batch, i); value = dex_future_get_value (future, &local_error); if (value != NULL) g_ptr_array_add (entries, g_value_dup_object (value)); else { g_warning ("Unable to retrieve cached entry: %s", local_error->message); g_clear_error (&local_error); } } if (batch->len < CACHE_ENUM_BATCH_SIZE) break; } return dex_future_new_take_boxed (G_TYPE_PTR_ARRAY, g_steal_pointer (&entries)); } static DexFuture * enumerate_disk_entries_fiber (BzWeakRef *wr) { g_autoptr (BzApplication) self = NULL; g_autoptr (GError) local_error = NULL; g_autoptr (GPtrArray) entries = NULL; gboolean has_flathub_entry = FALSE; bz_weak_get_or_return_reject (self, &wr->ref); entries = dex_await_boxed ( dex_limiter_run ( bz_get_io_limiter (), bz_get_io_scheduler (), bz_get_dex_stack_size (), (DexFiberFunc) enumerate_disk_io_fiber, g_object_ref (self->cache), g_object_unref), &local_error); if (entries == NULL) { g_warning ("Unable to enumerate cached entries: %s", local_error->message); return dex_future_new_for_error (g_steal_pointer (&local_error)); } for (guint i = 0; i < entries->len; i++) { BzEntry *entry = NULL; entry = g_ptr_array_index (entries, i); if (BZ_IS_FLATPAK_ENTRY (entry) && bz_flatpak_entry_get_bundle_path (BZ_FLATPAK_ENTRY (entry)) != NULL) /* refrain from restoring bundle entries */ continue; if (!has_flathub_entry && BZ_IS_FLATPAK_ENTRY (entry) && g_strcmp0 (bz_entry_get_remote_repo_name (entry), "flathub") == 0) has_flathub_entry = TRUE; fiber_replace_entry (self, entry); } gtk_filter_changed (GTK_FILTER (self->group_filter), GTK_FILTER_CHANGE_LESS_STRICT); gtk_filter_changed (GTK_FILTER (self->appid_filter), GTK_FILTER_CHANGE_LESS_STRICT); dex_future_disown (dex_scheduler_spawn ( dex_scheduler_get_default (), bz_get_dex_stack_size (), (DexFiberFunc) check_for_updates_fiber, bz_weak_ref_ref (wr), (GDestroyNotify) bz_weak_ref_unref)); return dex_future_new_for_boolean (has_flathub_entry); } static DexFuture * check_for_updates_fiber (BzWeakRef *wr) { g_autoptr (BzApplication) self = NULL; bz_weak_get_or_return_reject (self, &wr->ref); fiber_check_for_updates (self); finish_with_background_task_label (self); return dex_future_new_true (); } static DexFuture * cache_flathub_fiber (BzWeakRef *wr) { g_autoptr (BzApplication) self = NULL; g_autoptr (GError) local_error = NULL; gboolean result = FALSE; g_autofree char *flathub_cache = NULL; g_autoptr (GFile) flathub_cache_file = NULL; bz_weak_get_or_return_reject (self, &wr->ref); flathub_cache_file = fiber_dup_cache_file ("flathub-cache", &flathub_cache, &local_error); if (flathub_cache_file != NULL) { g_autoptr (GVariantBuilder) builder = NULL; g_autoptr (GVariant) variant = NULL; g_autoptr (GBytes) bytes = NULL; builder = g_variant_builder_new (G_VARIANT_TYPE_VARDICT); bz_serializable_serialize (BZ_SERIALIZABLE (self->flathub), builder); variant = g_variant_builder_end (builder); bytes = g_variant_get_data_as_bytes (variant); result = dex_await ( dex_file_replace_contents_bytes ( flathub_cache_file, bytes, NULL, FALSE, G_FILE_CREATE_REPLACE_DESTINATION), &local_error); if (!result) { g_warning ("Failed to cache flathub state to %s: %s", flathub_cache, local_error->message); g_clear_error (&local_error); } } else { g_warning ("Unable to ensure cache directory: %s", local_error->message); g_clear_error (&local_error); } return dex_future_new_true (); } static DexFuture * cache_groups_fiber (BzWeakRef *wr) { g_autoptr (BzApplication) self = NULL; g_autoptr (GError) local_error = NULL; gboolean result = FALSE; g_autofree char *groups_cache = NULL; g_autoptr (GFile) groups_cache_file = NULL; g_autoptr (GVariantBuilder) builder = NULL; g_autoptr (GVariant) variant = NULL; g_autoptr (GBytes) bytes = NULL; guint n_groups = 0; g_autoptr (GError) index_error = NULL; g_autofree char *module_dir = NULL; g_autofree char *index_path = NULL; bz_weak_get_or_return_reject (self, &wr->ref); groups_cache_file = fiber_dup_cache_file ("groups-cache", &groups_cache, &local_error); if (groups_cache_file == NULL) { g_warning ("Unable to ensure groups cache directory: %s", local_error->message); return dex_future_new_true (); } builder = g_variant_builder_new (G_VARIANT_TYPE ("aa{sv}")); n_groups = g_list_model_get_n_items (G_LIST_MODEL (self->groups)); for (guint i = 0; i < n_groups; i++) { g_autoptr (BzEntryGroup) group = NULL; g_autoptr (GVariantBuilder) gb = NULL; group = g_list_model_get_item (G_LIST_MODEL (self->groups), i); gb = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); bz_entry_group_serialize (group, gb); g_variant_builder_add (builder, "@a{sv}", g_variant_builder_end (gb)); } variant = g_variant_builder_end (builder); bytes = g_variant_get_data_as_bytes (variant); result = dex_await ( dex_file_replace_contents_bytes ( groups_cache_file, bytes, NULL, FALSE, G_FILE_CREATE_REPLACE_DESTINATION), &local_error); if (!result) g_warning ("Failed to cache groups to %s: %s", groups_cache, local_error->message); module_dir = bz_dup_module_dir (); index_path = g_build_filename (module_dir, "search-index", NULL); if (!bz_write_search_index (G_LIST_MODEL (self->groups), index_path, &index_error)) g_warning ("Failed to write search index to %s: %s", index_path, index_error->message); return dex_future_new_true (); } static DexFuture * respond_to_flatpak_fiber (BzWeakRef *wr, BzBackendNotification *notif) { g_autoptr (BzApplication) self = NULL; g_autoptr (GError) local_error = NULL; g_autoptr (GPtrArray) build_futures = NULL; g_autoptr (GPtrArray) build_notify_groups = NULL; g_autoptr (DexFuture) read_future = NULL; g_autoptr (DexFuture) reread_timeout = NULL; gboolean update_labels = FALSE; gboolean update_filters = FALSE; bz_weak_get_or_return_reject (self, &wr->ref); build_futures = g_ptr_array_new_with_free_func (dex_unref); build_notify_groups = g_ptr_array_new_with_free_func (g_object_unref); read_future = dex_future_new_for_object (notif); /* `reread_timeout` defines how long we are allowed to spend adding to `build-futures` before we update the UI later */ reread_timeout = dex_timeout_new_msec (100); for (;;) { BzBackendNotificationKind kind = 0; if (!dex_future_is_resolved (read_future)) { g_autoptr (DexFuture) future = NULL; future = dex_future_all_race ( dex_ref (reread_timeout), dex_ref (read_future), NULL); dex_await (g_steal_pointer (&future), NULL); if (!dex_future_is_pending (reread_timeout)) break; } notif = g_value_get_object (dex_future_get_value (read_future, NULL)); kind = bz_backend_notification_get_kind (notif); switch (kind) { case BZ_BACKEND_NOTIFICATION_KIND_ERROR: { const char *error = NULL; GtkWindow *window = NULL; error = bz_backend_notification_get_error (notif); if (error == NULL) break; g_warning ("Received an error from the flatpak backend: %s", error); window = gtk_application_get_active_window (GTK_APPLICATION (self)); if (window != NULL) bz_show_error_for_widget (GTK_WIDGET (window), _ ("A backend error occurred"), error); } break; case BZ_BACKEND_NOTIFICATION_KIND_TELL_INCOMING: { int n_incoming = 0; n_incoming = bz_backend_notification_get_n_incoming (notif); self->n_entries_incoming += n_incoming; update_labels = TRUE; } break; case BZ_BACKEND_NOTIFICATION_KIND_REPLACE_ENTRY: { BzEntry *entry = NULL; entry = bz_backend_notification_get_entry (notif); fiber_replace_entry (self, entry); g_ptr_array_add (build_futures, bz_entry_cache_manager_add (self->cache, entry)); if (bz_entry_is_of_kinds (entry, BZ_ENTRY_KIND_APPLICATION)) { const char *id = NULL; BzEntryGroup *group = NULL; update_filters = TRUE; id = bz_entry_get_id (entry); group = g_hash_table_lookup (self->ids_to_groups, id); if (group != NULL) g_ptr_array_add (build_notify_groups, g_object_ref (group)); } self->n_entries_incoming--; update_labels = TRUE; } break; case BZ_BACKEND_NOTIFICATION_KIND_REMOTE_SYNC_START: { const char *remote_name = NULL; remote_name = bz_backend_notification_get_remote_name (notif); if (bz_state_info_get_has_flathub (self->state)) /* We only count instances of the "flathub" remote if we have flathub, since that's all we care about for the UI */ { if (g_strcmp0 (remote_name, "flathub") == 0) self->n_remotes_syncing++; } else self->n_remotes_syncing++; g_debug ("remote '%s' has begun synchronization; " "now currently syncing %u remote(s)", remote_name, self->n_remotes_syncing); } break; case BZ_BACKEND_NOTIFICATION_KIND_REMOTE_SYNC_FINISH: { const char *remote_name = NULL; remote_name = bz_backend_notification_get_remote_name (notif); if (bz_state_info_get_has_flathub (self->state)) { if (g_strcmp0 (remote_name, "flathub") == 0) { self->n_remotes_syncing--; if (self->n_remotes_syncing == 0) self->flathub_remote_initialized = TRUE; } } else { self->n_remotes_syncing--; if (self->n_remotes_syncing == 0) bz_state_info_set_busy (self->state, FALSE); } g_debug ("remote '%s' has finished synchronization; " "now currently syncing %u remote(s)", remote_name, self->n_remotes_syncing); } break; case BZ_BACKEND_NOTIFICATION_KIND_INSTALL_DONE: case BZ_BACKEND_NOTIFICATION_KIND_UPDATE_DONE: case BZ_BACKEND_NOTIFICATION_KIND_REMOVE_DONE: { const char *unique_id = NULL; g_autoptr (BzEntry) entry = NULL; unique_id = bz_backend_notification_get_unique_id (notif); entry = dex_await_object ( bz_entry_cache_manager_get (self->cache, unique_id), &local_error); if (entry == NULL) { g_warning ("Backend notification references an entry " "which couldn't be decached: %s", local_error->message); break; } switch (kind) { case BZ_BACKEND_NOTIFICATION_KIND_INSTALL_DONE: { const char *version = NULL; version = bz_backend_notification_get_version (notif); g_hash_table_replace (self->installed_set, g_strdup (unique_id), g_strdup (version)); bz_entry_set_installed_version (entry, version); bz_entry_set_installed (entry, TRUE); if (bz_entry_is_of_kinds (entry, BZ_ENTRY_KIND_APPLICATION)) { BzEntryGroup *group = NULL; group = g_hash_table_lookup (self->ids_to_groups, bz_entry_get_id (entry)); if (group != NULL) { gboolean found = FALSE; guint position = 0; found = g_list_store_find (self->installed_apps, group, &position); if (!found) g_list_store_insert_sorted (self->installed_apps, group, (GCompareDataFunc) cmp_group, NULL); } } } break; case BZ_BACKEND_NOTIFICATION_KIND_UPDATE_DONE: case BZ_BACKEND_NOTIFICATION_KIND_REMOVE_DONE: { gboolean was_rebased = FALSE; was_rebased = bz_backend_notification_get_was_rebased (notif); if (kind == BZ_BACKEND_NOTIFICATION_KIND_UPDATE_DONE) { const char *version = NULL; version = bz_backend_notification_get_version (notif); g_hash_table_replace (self->installed_set, g_strdup (unique_id), g_strdup (version)); } if (kind == BZ_BACKEND_NOTIFICATION_KIND_REMOVE_DONE || was_rebased) { bz_entry_set_installed_version (entry, NULL); bz_entry_set_installed (entry, FALSE); g_hash_table_remove (self->installed_set, unique_id); if (bz_entry_is_of_kinds (entry, BZ_ENTRY_KIND_APPLICATION)) { BzEntryGroup *group = NULL; group = g_hash_table_lookup (self->ids_to_groups, bz_entry_get_id (entry)); if (group != NULL && (was_rebased || !bz_entry_group_get_removable (group))) { gboolean found = FALSE; guint position = 0; found = g_list_store_find (self->installed_apps, group, &position); if (found) g_list_store_remove (self->installed_apps, position); } } } } break; 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_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 (); }; g_ptr_array_add (build_futures, bz_entry_cache_manager_add (self->cache, entry)); if (bz_entry_is_of_kinds (entry, BZ_ENTRY_KIND_APPLICATION)) { const char *id = NULL; BzEntryGroup *group = NULL; id = bz_entry_get_id (entry); group = g_hash_table_lookup (self->ids_to_groups, id); if (group != NULL) g_ptr_array_add (build_notify_groups, g_object_ref (group)); } } 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 }; GHashTableIter new_iter = { 0 }; g_autoptr (GPtrArray) diff_writes = NULL; 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), &local_error); if (installed_set == NULL) { g_warning ("Failed to enumerate installed entries: %s", local_error->message); finish_with_background_task_label (self); break; } diff_reads = g_ptr_array_new_with_free_func (dex_unref); g_hash_table_iter_init (&old_iter, self->installed_set); for (;;) { char *unique_id = NULL; if (!g_hash_table_iter_next ( &old_iter, (gpointer *) &unique_id, NULL)) break; if (!g_hash_table_contains (installed_set, unique_id)) g_ptr_array_add ( diff_reads, bz_entry_cache_manager_get (self->cache, unique_id)); } g_hash_table_iter_init (&new_iter, installed_set); for (;;) { char *unique_id = NULL; if (!g_hash_table_iter_next ( &new_iter, (gpointer *) &unique_id, NULL)) break; if (!g_hash_table_contains (self->installed_set, unique_id)) g_ptr_array_add ( diff_reads, bz_entry_cache_manager_get (self->cache, unique_id)); } if (diff_reads->len > 0) { dex_await (dex_future_allv ( (DexFuture *const *) diff_reads->pdata, diff_reads->len), NULL); diff_writes = g_ptr_array_new_with_free_func (dex_unref); for (guint i = 0; i < diff_reads->len; i++) { DexFuture *future = NULL; future = g_ptr_array_index (diff_reads, i); if (dex_future_is_resolved (future)) { BzEntry *entry = NULL; const char *id = NULL; const char *unique_id = NULL; BzEntryGroup *group = NULL; gboolean installed = FALSE; const char *version = NULL; entry = g_value_get_object (dex_future_get_value (future, NULL)); id = bz_entry_get_id (entry); group = g_hash_table_lookup (self->ids_to_groups, id); if (group != NULL) bz_entry_group_connect_living (group, entry); unique_id = bz_entry_get_unique_id (entry); installed = g_hash_table_contains (installed_set, unique_id); version = g_hash_table_lookup (installed_set, unique_id); if (installed && version != NULL && *version != '\0') bz_entry_set_installed_version (entry, version); bz_entry_set_installed (entry, installed); if (group != NULL) { gboolean found = FALSE; guint position = 0; found = g_list_store_find (self->installed_apps, group, &position); if (installed && !found) g_list_store_insert_sorted ( self->installed_apps, group, (GCompareDataFunc) cmp_group, NULL); else if (!installed && found && bz_entry_group_get_removable (group) == 0) g_list_store_remove (self->installed_apps, position); } g_ptr_array_add ( diff_writes, bz_entry_cache_manager_add (self->cache, entry)); } } dex_await (dex_future_allv ( (DexFuture *const *) diff_writes->pdata, diff_writes->len), NULL); } g_clear_pointer (&self->installed_set, g_hash_table_unref); self->installed_set = g_steal_pointer (&installed_set); fiber_check_for_updates (self); finish_with_background_task_label (self); } break; default: g_assert_not_reached (); } dex_clear (&read_future); read_future = dex_channel_receive (self->flatpak_notifs); if (!dex_future_is_pending (reread_timeout)) break; } if (build_futures->len > 0) { g_autoptr (DexFuture) future = NULL; future = dex_future_allv ( (DexFuture *const *) build_futures->pdata, build_futures->len); future = bz_future_finallyv ( future, G_CALLBACK (cache_write_back_finally), 3, BZ_TYPE_WEAK_REF, wr, G_TYPE_PTR_ARRAY, build_notify_groups, G_TYPE_BOOLEAN, update_filters); dex_future_disown (g_steal_pointer (&future)); } if (update_labels) { if (self->n_entries_incoming > 0) bz_state_info_set_background_task_label_take_printf ( self->state, _ ("Loading %d apps…"), self->n_entries_incoming); else { fiber_check_for_updates (self); finish_with_background_task_label (self); } } if (self->n_entries_incoming == 0 && self->flathub_remote_initialized) { bz_state_info_set_busy (self->state, FALSE); finish_with_background_task_label (self); } return g_steal_pointer (&read_future); } static DexFuture * open_appstream_fiber (BzWeakRef *wr, const char *id) { g_autoptr (BzApplication) self = NULL; gboolean had_to_wait = FALSE; bz_weak_get_or_return_reject (self, &wr->ref); had_to_wait = !dex_future_is_resolved (DEX_FUTURE (self->ready_to_open_files)); dex_await (dex_ref (self->ready_to_open_files), NULL); open_generic_id (self, id, !had_to_wait); return dex_future_new_true (); } static DexFuture * open_flatpakref_fiber (BzWeakRef *wr, GFile *file) { g_autoptr (BzApplication) self = NULL; g_autoptr (GError) local_error = NULL; g_autoptr (DexFuture) future = NULL; const GValue *value = NULL; gboolean had_to_wait = FALSE; bz_weak_get_or_return_reject (self, &wr->ref); had_to_wait = !dex_future_is_resolved (DEX_FUTURE (self->ready_to_open_files)); dex_await (dex_ref (self->ready_to_open_files), NULL); future = bz_backend_load_local_package (BZ_BACKEND (self->flatpak), file, NULL); dex_await (dex_ref (future), NULL); value = dex_future_get_value (future, &local_error); if (value == NULL) { GtkWindow *window = get_or_create_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; BzFlatpakRepo *repo = NULL; BzBundleInstallDialog *dialog = NULL; BzFlatpakBundleResult *bundle_result = NULL; const char *id = NULL; window = get_or_create_window (self); bundle_result = BZ_FLATPAK_BUNDLE_RESULT (g_value_get_object (value)); entry = BZ_ENTRY (bz_flatpak_bundle_result_get_entry (bundle_result)); repo = bz_flatpak_bundle_result_get_runtime_repo (bundle_result); id = bz_entry_get_id (entry); if (id != NULL) { BzEntryGroup *group = NULL; group = g_hash_table_lookup (self->ids_to_groups, id); if (group != NULL && bz_entry_group_get_removable (group) > 0) bz_entry_set_installed (entry, TRUE); } dialog = g_object_new ( BZ_TYPE_BUNDLE_INSTALL_DIALOG, "state", self->state, "entry", entry, "runtime-repo", repo, NULL); adw_dialog_present (ADW_DIALOG (dialog), GTK_WIDGET (window)); } else if (G_VALUE_HOLDS_STRING (value)) { const char *id = NULL; id = g_value_get_string (value); if (id != NULL) open_generic_id (self, id, !had_to_wait); } return dex_future_new_true (); } static DexFuture * open_metainfo_fiber (BzWeakRef *wr, GFile *file) { g_autoptr (BzApplication) self = NULL; g_autoptr (GError) local_error = NULL; g_autoptr (DexFuture) pick_future = NULL; const GValue *value = NULL; BzMetainfoPickResult *result = NULL; g_autoptr (BzEntry) entry = NULL; GtkWindow *window = NULL; bz_weak_get_or_return_reject (self, &wr->ref); dex_await (dex_ref (self->ready_to_open_files), NULL); window = get_or_create_window (self); pick_future = bz_metainfo_preview_open_file (file, window); dex_await (dex_ref (pick_future), NULL); value = dex_future_get_value (pick_future, &local_error); if (value == NULL) { 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)); } result = g_value_get_boxed (value); entry = bz_appstream_parser_entry_from_metainfo ( result->metainfo_file, result->icon_file, &local_error); if (entry == NULL) { bz_show_error_for_widget ( GTK_WIDGET (window), _ ("Failed to load metainfo"), local_error->message); return dex_future_new_for_error (g_steal_pointer (&local_error)); } g_object_set (entry, "id", "preview", NULL); bz_window_show_entry (BZ_WINDOW (window), entry); return dex_future_new_true (); } static DexFuture * show_update_history_fiber (BzWeakRef *wr, GVariant *history) { g_autoptr (BzApplication) self = NULL; GtkWindow *window = NULL; bz_weak_get_or_return_reject (self, &wr->ref); dex_await (dex_ref (self->ready_to_open_files), NULL); window = get_or_create_window (self); // TODO: implement this in the get or create window func if (adw_application_window_get_visible_dialog (ADW_APPLICATION_WINDOW (window)) != NULL) window = new_window (self); bz_window_show_update_history (BZ_WINDOW (window), history); return dex_future_new_true (); } static DexFuture * init_fiber_finally (DexFuture *future, BzWeakRef *wr) { g_autoptr (BzApplication) self = NULL; g_autoptr (GError) local_error = NULL; const GValue *value = NULL; gint64 last_refresh = 0; gint64 now = 0; gint64 seconds_since_refresh = 0; bz_weak_get_or_return_reject (self, &wr->ref); value = dex_future_get_value (future, &local_error); if (value != NULL) { self->flatpak_notifs = bz_backend_create_notification_channel ( BZ_BACKEND (self->flatpak)); self->notif_watch = dex_future_then_loop ( dex_channel_receive (self->flatpak_notifs), (DexFutureCallback) watch_backend_notifs_then_loop_cb, bz_weak_ref_ref (wr), (GDestroyNotify) bz_weak_ref_unref); last_refresh = g_settings_get_int64 (self->settings, "last-refresh-time"); now = g_get_real_time () / G_USEC_PER_SEC; seconds_since_refresh = now - last_refresh; if ((!bz_state_info_get_metered_connection (self->state) || !self->had_cache_on_init) && seconds_since_refresh >= MIN_STARTUP_REFRESH_INTERVAL_SECONDS) { g_autoptr (DexFuture) sync_future = NULL; sync_future = make_sync_future (self); sync_future = dex_future_finally ( sync_future, (DexFutureCallback) init_sync_finally, bz_weak_ref_ref (wr), (GDestroyNotify) bz_weak_ref_unref); self->sync = g_steal_pointer (&sync_future); } else { bz_state_info_set_allow_manual_sync (self->state, TRUE); bz_state_info_set_busy (self->state, FALSE); bz_state_info_set_syncing (self->state, FALSE); dex_promise_resolve_boolean (self->ready_to_open_files, TRUE); if (self->holding_for_no_window_refresh) { self->holding_for_no_window_refresh = FALSE; g_application_release (G_APPLICATION (self)); } // Only check for updates if we skipped a full sync. dex_future_disown (dex_scheduler_spawn ( dex_scheduler_get_default (), bz_get_dex_stack_size (), (DexFiberFunc) check_for_updates_fiber, bz_weak_ref_ref (wr), (GDestroyNotify) bz_weak_ref_unref)); } self->periodic_timeout_source = g_timeout_add_seconds ( /* Check every day */ 60 * 60 * 24, (GSourceFunc) periodic_timeout_cb, self); bz_malcontent_service_start (self->malcontent); g_object_bind_property ( bz_state_info_get_auth_state (self->state), "authenticated", g_action_map_lookup_action (G_ACTION_MAP (self), "flathub-login"), "enabled", G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN); } else { GtkWindow *window = NULL; bz_state_info_set_online (self->state, FALSE); bz_state_info_set_busy (self->state, FALSE); g_warning ("Could not initialize: %s", local_error->message); window = gtk_application_get_active_window (GTK_APPLICATION (self)); if (window != NULL) { g_autofree char *error_string = NULL; error_string = g_strdup_printf ( "Could not initialize: %s", local_error->message); bz_show_error_for_widget (GTK_WIDGET (window), _ ("An initialization error occurred"), error_string); } if (self->holding_for_no_window_refresh) { self->holding_for_no_window_refresh = FALSE; g_application_release (G_APPLICATION (self)); } } return dex_future_new_true (); } static DexFuture * init_sync_finally (DexFuture *future, BzWeakRef *wr) { g_autoptr (BzApplication) self = NULL; g_autoptr (GError) local_error = NULL; bz_weak_get_or_return_reject (self, &wr->ref); /* Do nothing */ return dex_future_new_true (); } static DexFuture * backend_sync_finally (DexFuture *future, BzWeakRef *wr) { g_autoptr (BzApplication) self = NULL; bz_weak_get_or_return_reject (self, &wr->ref); if (dex_future_is_resolved (future)) { g_autoptr (DexFuture) enum_future = NULL; enum_future = dex_scheduler_spawn ( dex_scheduler_get_default (), bz_get_dex_stack_size (), (DexFiberFunc) enumerate_disk_entries_fiber, bz_weak_ref_ref (wr), (GDestroyNotify) bz_weak_ref_unref); enum_future = dex_future_finally ( enum_future, (DexFutureCallback) backend_sync_save_groups_finally, bz_weak_ref_ref (wr), (GDestroyNotify) bz_weak_ref_unref); return g_steal_pointer (&enum_future); } else return dex_ref (future); } static DexFuture * backend_sync_save_groups_finally (DexFuture *future, BzWeakRef *wr) { g_autoptr (BzApplication) self = NULL; bz_weak_get_or_return_reject (self, &wr->ref); return dex_scheduler_spawn ( dex_scheduler_get_default (), bz_get_dex_stack_size (), (DexFiberFunc) cache_groups_fiber, bz_weak_ref_ref (wr), (GDestroyNotify) bz_weak_ref_unref); } static DexFuture * flathub_update_finally (DexFuture *future, BzWeakRef *wr) { g_autoptr (BzApplication) self = NULL; bz_weak_get_or_return_reject (self, &wr->ref); if (dex_future_is_resolved (future)) { g_clear_object (&self->flathub); g_assert (self->tmp_flathub != NULL); self->flathub = g_steal_pointer (&self->tmp_flathub); bz_flathub_state_set_map_factory (self->flathub, self->application_factory); bz_state_info_set_flathub (self->state, self->flathub); return dex_scheduler_spawn ( dex_scheduler_get_default (), bz_get_dex_stack_size (), (DexFiberFunc) cache_flathub_fiber, bz_weak_ref_ref (wr), (GDestroyNotify) bz_weak_ref_unref); } else { g_clear_object (&self->tmp_flathub); return dex_ref (future); } } static DexFuture * cache_write_back_finally (DexFuture *future, BzWeakRef *wr, GPtrArray *notify_groups, gboolean update_filters) { g_autoptr (BzApplication) self = NULL; bz_weak_get_or_return_reject (self, &wr->ref); for (guint i = 0; i < notify_groups->len; i++) { BzEntryGroup *group = NULL; group = g_ptr_array_index (notify_groups, i); g_object_notify (G_OBJECT (group), "ui-entry"); } if (update_filters) { gtk_filter_changed (GTK_FILTER (self->group_filter), GTK_FILTER_CHANGE_LESS_STRICT); gtk_filter_changed (GTK_FILTER (self->appid_filter), GTK_FILTER_CHANGE_LESS_STRICT); } return dex_future_new_true (); } static DexFuture * sync_finally (DexFuture *future, BzWeakRef *wr) { g_autoptr (BzApplication) self = NULL; bz_weak_get_or_return_reject (self, &wr->ref); bz_state_info_set_online (self->state, dex_future_is_resolved (future)); bz_state_info_set_allow_manual_sync (self->state, TRUE); bz_state_info_set_busy (self->state, FALSE); bz_state_info_set_syncing (self->state, FALSE); finish_with_background_task_label (self); if (dex_future_is_resolved (future)) g_settings_set_int64 ( self->settings, "last-refresh-time", g_get_real_time () / G_USEC_PER_SEC); dex_promise_resolve_boolean (self->ready_to_open_files, TRUE); if (self->holding_for_no_window_refresh) { self->holding_for_no_window_refresh = FALSE; g_application_release (G_APPLICATION (self)); } return dex_future_new_true (); } static DexFuture * watch_backend_notifs_then_loop_cb (DexFuture *future, BzWeakRef *wr) { g_autoptr (BzApplication) self = NULL; g_autoptr (GError) local_error = NULL; const GValue *value = NULL; BzBackendNotification *notif = NULL; g_autoptr (DexFuture) ret_future = NULL; bz_weak_get_or_return_reject (self, &wr->ref); value = dex_future_get_value (future, NULL); g_assert (value != NULL); notif = g_value_get_object (value); ret_future = dex_scheduler_spawnv ( dex_scheduler_get_default (), bz_get_dex_stack_size (), G_CALLBACK (respond_to_flatpak_fiber), 2, BZ_TYPE_WEAK_REF, wr, BZ_TYPE_BACKEND_NOTIFICATION, notif); return g_steal_pointer (&ret_future); } static BzEntryGroup * ensure_group_and_add (BzApplication *self, const char *id, BzEntry *entry, BzEntry *eol_runtime, gboolean ignore_eol, gboolean installed) { BzEntryGroup *group = NULL; group = g_hash_table_lookup (self->ids_to_groups, id); if (group != NULL) bz_entry_group_add (group, entry, eol_runtime, ignore_eol); else { g_autoptr (BzEntryGroup) new_group = NULL; g_debug ("Creating new application group for id %s", id); new_group = bz_entry_group_new (self->entry_factory); bz_entry_group_add (new_group, entry, eol_runtime, ignore_eol); g_list_store_append (self->groups, new_group); g_hash_table_replace (self->ids_to_groups, g_strdup (id), g_object_ref (new_group)); group = new_group; } if (installed && !g_list_store_find (self->installed_apps, group, NULL)) g_list_store_insert_sorted ( self->installed_apps, group, (GCompareDataFunc) cmp_group, NULL); return group; } static void fiber_replace_entry (BzApplication *self, BzEntry *entry) { const char *id = NULL; const char *unique_id = NULL; const char *unique_id_checksum = NULL; gboolean user = FALSE; gboolean installed = FALSE; const char *flatpak_id = NULL; const char *version = NULL; GHashTable *name_to_addons = NULL; id = bz_entry_get_id (entry); unique_id = bz_entry_get_unique_id (entry); unique_id_checksum = bz_entry_get_unique_id_checksum (entry); if (id == NULL || unique_id == NULL || unique_id_checksum == NULL) return; user = bz_flatpak_entry_is_user (BZ_FLATPAK_ENTRY (entry)); name_to_addons = user ? self->usr_name_to_addons : self->sys_name_to_addons; installed = g_hash_table_contains (self->installed_set, unique_id); bz_entry_set_installed (entry, installed); version = g_hash_table_lookup (self->installed_set, unique_id); if (version != NULL && *version != '\0') bz_entry_set_installed_version (entry, version); flatpak_id = bz_flatpak_entry_get_flatpak_id (BZ_FLATPAK_ENTRY (entry)); if (flatpak_id != NULL) { GPtrArray *addons = NULL; addons = g_hash_table_lookup (name_to_addons, flatpak_id); if (addons != NULL) { g_debug ("Appending %d addons to %s", addons->len, unique_id); for (guint i = 0; i < addons->len; i++) { const char *addon_id = NULL; addon_id = g_ptr_array_index (addons, i); bz_entry_append_addon (entry, addon_id); } g_hash_table_remove (name_to_addons, flatpak_id); addons = NULL; } } if (bz_entry_is_of_kinds (entry, BZ_ENTRY_KIND_APPLICATION)) { gboolean ignore_eol = FALSE; const char *runtime_name = NULL; g_autoptr (BzEntry) eol_runtime = NULL; BzEntryGroup *group = NULL; GHashTable *ref_to_addon_group_ids = NULL; GPtrArray *pending = NULL; if (self->ignore_eol_set != NULL) ignore_eol = g_hash_table_contains (self->ignore_eol_set, id); runtime_name = bz_flatpak_entry_get_application_runtime (BZ_FLATPAK_ENTRY (entry)); if (!ignore_eol && runtime_name != NULL) { char *runtime_checksum = NULL; runtime_checksum = g_hash_table_lookup (self->eol_runtimes, runtime_name); if (runtime_checksum != NULL) eol_runtime = dex_await_object ( bz_entry_cache_manager_get_by_checksum ( self->cache, runtime_checksum), NULL); } group = ensure_group_and_add (self, id, entry, eol_runtime, ignore_eol, installed); ref_to_addon_group_ids = user ? self->usr_ref_to_addon_group_ids : self->sys_ref_to_addon_group_ids; pending = g_hash_table_lookup (ref_to_addon_group_ids, id); if (pending != NULL) { for (guint i = 0; i < pending->len; i++) bz_entry_group_append_addon_group_id (group, g_ptr_array_index (pending, i)); g_hash_table_remove (ref_to_addon_group_ids, id); } } if (flatpak_id != NULL && bz_entry_is_of_kinds (entry, BZ_ENTRY_KIND_RUNTIME) && g_str_has_prefix (flatpak_id, "runtime/")) { const char *stripped = NULL; const char *eol = NULL; stripped = flatpak_id + strlen ("runtime/"); eol = bz_entry_get_eol (entry); if (eol != NULL) g_hash_table_replace ( self->eol_runtimes, g_strdup (stripped), g_strdup (unique_id_checksum)); else g_hash_table_remove (self->eol_runtimes, stripped); } if (bz_entry_is_of_kinds (entry, BZ_ENTRY_KIND_ADDON) && strstr (id, ".Debug") == NULL && strstr (id, ".Locale") == NULL) { const char *extension_of_what = NULL; extension_of_what = bz_flatpak_entry_get_addon_extension_of_ref ( BZ_FLATPAK_ENTRY (entry)); if (extension_of_what != NULL && g_str_has_prefix (extension_of_what, "app/")) { g_auto (GStrv) parts = NULL; BzEntryGroup *app_group = NULL; ensure_group_and_add (self, id, entry, NULL, FALSE, installed); parts = g_strsplit (extension_of_what, "/", -1); if (parts != NULL && parts[1] != NULL) { app_group = g_hash_table_lookup (self->ids_to_groups, parts[1]); if (app_group != NULL) bz_entry_group_append_addon_group_id (app_group, id); else { GHashTable *ref_to_addon_group_ids = user ? self->usr_ref_to_addon_group_ids : self->sys_ref_to_addon_group_ids; GPtrArray *pending = NULL; pending = g_hash_table_lookup (ref_to_addon_group_ids, parts[1]); if (pending == NULL) { pending = g_ptr_array_new_with_free_func (g_free); g_hash_table_replace (ref_to_addon_group_ids, g_strdup (parts[1]), pending); } g_ptr_array_add (pending, g_strdup (id)); } } } if (extension_of_what != NULL) { GPtrArray *addons = NULL; /* BzFlatpakInstance ensures addons come before applications */ addons = g_hash_table_lookup (name_to_addons, extension_of_what); if (addons == NULL) { addons = g_ptr_array_new_with_free_func (g_free); g_hash_table_replace ( name_to_addons, g_strdup (extension_of_what), addons); } g_ptr_array_add (addons, g_strdup (unique_id)); } else g_warning ("Entry with unique id %s is an addon but " "does not seem to extend anything", unique_id); } } static void fiber_check_for_updates (BzApplication *self) { g_autoptr (GError) local_error = NULL; g_autoptr (GHashTable) update_ids = NULL; GtkWindow *window = NULL; g_debug ("Checking for updates..."); bz_state_info_set_background_task_label (self->state, _ ("Checking for updates…")); bz_state_info_set_checking_for_updates (self->state, TRUE); update_ids = dex_await_boxed ( bz_backend_retrieve_update_ids (BZ_BACKEND (self->flatpak), NULL), &local_error); window = gtk_application_get_active_window (GTK_APPLICATION (self)); if (update_ids != NULL && g_hash_table_size (update_ids) > 0) { g_autoptr (GPtrArray) unique_ids = NULL; g_autoptr (GPtrArray) futures = NULL; g_autoptr (GListStore) store = NULL; GHashTableIter iter; gpointer key = NULL; gpointer value = NULL; unique_ids = g_ptr_array_new (); futures = g_ptr_array_new_with_free_func (dex_unref); g_hash_table_iter_init (&iter, update_ids); while (g_hash_table_iter_next (&iter, &key, &value)) { const char *unique_id = key; g_ptr_array_add (unique_ids, (gpointer) unique_id); g_ptr_array_add (futures, bz_entry_cache_manager_get (self->cache, unique_id)); } dex_await ( dex_future_allv ((DexFuture *const *) futures->pdata, futures->len), NULL); store = g_list_store_new (BZ_TYPE_UPDATE_PERMISSION_INFO); for (guint i = 0; i < futures->len; i++) { DexFuture *future = NULL; const GValue *value_out = NULL; const char *unique_id = NULL; future = g_ptr_array_index (futures, i); unique_id = g_ptr_array_index (unique_ids, i); value_out = dex_future_get_value (future, &local_error); if (value_out != NULL) { g_autoptr (BzUpdatePermissionInfo) info = NULL; BzAppPermissions *delta = NULL; delta = g_hash_table_lookup (update_ids, unique_id); info = g_object_new ( BZ_TYPE_UPDATE_PERMISSION_INFO, "entry", g_value_get_object (value_out), "additional-permissions", delta, NULL); g_list_store_append (store, info); } else { g_warning ("%s could not be resolved for the update list and thus will not be included: %s", unique_id, local_error->message); g_clear_pointer (&local_error, g_error_free); } } if (g_list_model_get_n_items (G_LIST_MODEL (store)) > 0) bz_state_info_set_available_updates (self->state, G_LIST_MODEL (store)); } else if (local_error != NULL) { g_warning ("Failed to check for updates: %s", local_error->message); if (window != NULL) bz_show_error_for_widget (GTK_WIDGET (window), _ ("Failed to check for updates"), local_error->message); } if (update_ids != NULL || local_error == NULL) g_settings_set_int64 ( self->settings, "last-update-check", g_get_real_time () / G_USEC_PER_SEC); bz_state_info_set_checking_for_updates (self->state, FALSE); } static GFile * fiber_dup_cache_file (const char *name, char **path_out, GError **error) { gboolean result = FALSE; g_autofree char *module_dir = NULL; g_autoptr (GFile) module_dir_file = NULL; g_autofree char *path = NULL; g_autoptr (GFile) file = NULL; module_dir = bz_dup_module_dir (); module_dir_file = g_file_new_for_path (module_dir); result = dex_await ( dex_file_make_directory_with_parents (module_dir_file), error); if (!result) return NULL; path = g_build_filename (module_dir, name, NULL); file = g_file_new_for_path (path); if (path_out != NULL) *path_out = g_steal_pointer (&path); return g_steal_pointer (&file); } static gboolean periodic_timeout_cb (BzApplication *self) { gboolean have_connection = FALSE; gboolean metered_connection = FALSE; if (self->sync != NULL && dex_future_is_pending (self->sync)) /* If for some reason the last update check is still happening, let it finish */ goto done; dex_clear (&self->sync); have_connection = bz_state_info_get_have_connection (self->state); metered_connection = bz_state_info_get_metered_connection (self->state); if (have_connection && !metered_connection) /* Do not do periodic sync on metered connections. The user will have to manually refresh instead. */ self->sync = make_sync_future (self); else bz_state_info_set_recently_synced (self->state, FALSE); done: return G_SOURCE_CONTINUE; } static gboolean scheduled_timeout_cb (BzWeakRef *wr) { g_autoptr (BzApplication) self = NULL; gboolean have_connection = FALSE; /* Use weak ref here since the source tag of this callback won't be tracked by the main application obj */ self = g_weak_ref_get (&wr->ref); if (self == NULL) goto done; dex_clear (&self->sync); have_connection = bz_state_info_get_have_connection (self->state); if (have_connection) self->sync = make_sync_future (self); done: return G_SOURCE_REMOVE; } static void network_status_changed (BzApplication *self, GParamSpec *pspec, GNetworkMonitor *network) { gboolean was_connected = FALSE; gboolean was_metered = FALSE; GNetworkConnectivity connectivity = 0; gboolean have_connection = FALSE; gboolean is_metered = FALSE; was_connected = bz_state_info_get_have_connection (self->state); was_metered = bz_state_info_get_metered_connection (self->state); connectivity = g_network_monitor_get_connectivity (network); have_connection = connectivity == G_NETWORK_CONNECTIVITY_FULL; is_metered = g_network_monitor_get_network_metered (network); if (!bz_state_info_get_busy (self->state) && ((!was_connected && have_connection && !is_metered) || (was_metered && !is_metered))) /* Wait a bit to prevent flakiness */ g_timeout_add_full ( G_PRIORITY_DEFAULT, 500, (GSourceFunc) scheduled_timeout_cb, bz_weak_ref_new (self), (GDestroyNotify) bz_weak_ref_unref); bz_state_info_set_have_connection (self->state, have_connection); bz_state_info_set_metered_connection (self->state, is_metered); } static void disable_blocklists_changed (BzApplication *self, GParamSpec *pspec, BzStateInfo *state) { gtk_filter_changed (GTK_FILTER (self->group_filter), GTK_FILTER_CHANGE_DIFFERENT); gtk_filter_changed (GTK_FILTER (self->appid_filter), GTK_FILTER_CHANGE_DIFFERENT); } static void transactions_active_changed (BzApplication *self, GParamSpec *pspec, BzTransactionManager *transactions) { if (bz_transaction_manager_get_active (transactions)) g_application_hold (G_APPLICATION (self)); else g_application_release (G_APPLICATION (self)); } static void show_hide_app_setting_changed (BzApplication *self, const char *key, GSettings *settings) { g_object_freeze_notify (G_OBJECT (self->state)); bz_state_info_set_hide_eol (self->state, g_settings_get_boolean (self->settings, "hide-eol")); bz_state_info_set_show_only_foss (self->state, g_settings_get_boolean (self->settings, "show-only-foss")); bz_state_info_set_show_only_flathub (self->state, g_settings_get_boolean (self->settings, "show-only-flathub")); bz_state_info_set_show_only_verified (self->state, g_settings_get_boolean (self->settings, "show-only-verified")); gtk_filter_changed (GTK_FILTER (self->group_filter), GTK_FILTER_CHANGE_DIFFERENT); gtk_filter_changed (GTK_FILTER (self->appid_filter), GTK_FILTER_CHANGE_DIFFERENT); g_object_thaw_notify (G_OBJECT (self->state)); } static gboolean window_close_request (BzApplication *self, GtkWidget *window) { int width = 0; int height = 0; GList *remaining_windows = NULL; gboolean reap_dl_workers = TRUE; width = gtk_widget_get_width (window); height = gtk_widget_get_height (window); g_settings_set (self->settings, "window-dimensions", "(ii)", width, height); remaining_windows = gtk_application_get_windows ( GTK_APPLICATION (self)); for (GList *l = remaining_windows; l != NULL; l = l->next) { if (l->data != window) { reap_dl_workers = FALSE; break; } } if (reap_dl_workers) /* If no windows are left, kill the dl-worker subprocesses to minimize idle memory usage */ bz_reap_default_download_workers (); /* Do not stop other handlers from being invoked for the signal */ return FALSE; } static void blocklists_changed (BzApplication *self, guint position, guint removed, guint added, GListModel *model) { g_autoptr (GError) local_error = NULL; if (removed > 0) g_ptr_array_remove_range (self->blocklist_regexes, position, removed); for (guint i = 0; i < added; i++) { g_autoptr (BzRootBlocklist) root = NULL; g_autoptr (GPtrArray) regex_datas = NULL; GListModel *blocklists = NULL; root = g_list_model_get_item (model, position + i); regex_datas = g_ptr_array_new_with_free_func (blocklist_regex_data_unref); blocklists = bz_root_blocklist_get_blocklists (root); if (blocklists != NULL) { guint n_blocklists = 0; n_blocklists = g_list_model_get_n_items (blocklists); for (guint j = 0; j < n_blocklists; j++) { g_autoptr (BzBlocklist) blocklist = NULL; GListModel *conditions = NULL; GListModel *allow = NULL; GListModel *allow_regex = NULL; GListModel *block = NULL; GListModel *block_regex = NULL; g_autoptr (BlocklistRegexData) data = NULL; blocklist = g_list_model_get_item (blocklists, j); allow = bz_blocklist_get_allow (blocklist); allow_regex = bz_blocklist_get_allow_regex (blocklist); block = bz_blocklist_get_block (blocklist); block_regex = bz_blocklist_get_block_regex (blocklist); if (allow == NULL && allow_regex == NULL && block == NULL && block_regex == NULL) { g_warning ("Blocklist file has an empty blocklist, ignoring"); continue; } conditions = bz_blocklist_get_conditions (blocklist); if (conditions != NULL) { guint n_conditions = 0; gboolean ignore = FALSE; n_conditions = g_list_model_get_n_items (conditions); for (guint k = 0; k < n_conditions; k++) { gboolean condition_result = FALSE; g_autoptr (BzBlocklistCondition) condition = NULL; BzBlocklistConditionMatchEnvvar *match_envvar = NULL; BzBlocklistConditionMatchLocale *match_locale = NULL; BzBlocklistConditionPostProcessKind postprocess = BZ_BLOCKLIST_CONDITION_POST_PROCESS_KIND_IDENTITY; condition = g_list_model_get_item (conditions, k); match_envvar = bz_blocklist_condition_get_match_envvar (condition); match_locale = bz_blocklist_condition_get_match_locale (condition); postprocess = bz_blocklist_condition_get_post_process (condition); if (match_envvar == NULL && match_locale == NULL) { g_warning ("Blocklist file has an empty condition"); continue; } if (!condition_result && match_envvar != NULL) { const char *var = NULL; const char *regex = NULL; var = bz_blocklist_condition_match_envvar_get_var (match_envvar); regex = bz_blocklist_condition_match_envvar_get_regex (match_envvar); if (var != NULL && regex != NULL) { g_autoptr (GRegex) compiled = NULL; const char *value = NULL; compiled = g_regex_new ( regex, G_REGEX_ANCHORED, G_REGEX_MATCH_ANCHORED, &local_error); if (compiled == NULL) { g_warning ("Blocklist condition contains invalid regex: %s", local_error->message); g_clear_error (&local_error); continue; } value = g_getenv (var); if (value != NULL && g_regex_match ( compiled, value, G_REGEX_MATCH_ANCHORED, NULL)) condition_result = TRUE; if (postprocess == BZ_BLOCKLIST_CONDITION_POST_PROCESS_KIND_INVERT) condition_result = !condition_result; } else g_warning ("Blocklist file has a envvar condition " "missing a var and/or a regex pattern"); } if (!condition_result && match_locale != NULL) { const char *regex = NULL; regex = bz_blocklist_condition_match_locale_get_regex (match_locale); if (regex != NULL) { g_autoptr (GRegex) compiled = NULL; const char *const *locales = NULL; compiled = g_regex_new ( regex, G_REGEX_ANCHORED, G_REGEX_MATCH_ANCHORED, &local_error); if (compiled == NULL) { g_warning ("Blocklist condition contains invalid regex: %s", local_error->message); g_clear_error (&local_error); continue; } locales = g_get_language_names (); for (guint l = 0; locales[l] != NULL; l++) { if (g_regex_match ( compiled, locales[i], G_REGEX_MATCH_ANCHORED, NULL)) condition_result = TRUE; if (condition_result) break; } if (postprocess == BZ_BLOCKLIST_CONDITION_POST_PROCESS_KIND_INVERT) condition_result = !condition_result; } else g_warning ("Blocklist file has a match-locale " "condition missing a regex pattern"); } if (!condition_result) { ignore = TRUE; break; } } if (ignore) continue; } data = blocklist_regex_data_new (); data->priority = bz_blocklist_get_priority (blocklist); #define BUILD_REGEX(_name, _builder) \ if (_name != NULL) \ { \ guint _n_strings = 0; \ \ _n_strings = g_list_model_get_n_items (_name); \ for (guint _i = 0; _i < _n_strings; _i++) \ { \ g_autoptr (GtkStringObject) _object = NULL; \ const char *_string = NULL; \ g_autoptr (GRegex) _regex = NULL; \ \ _object = g_list_model_get_item (_name, _i); \ _string = gtk_string_object_get_string (_object); \ _regex = g_regex_new (_string, G_REGEX_DEFAULT, G_REGEX_MATCH_DEFAULT, &local_error); \ \ if (_regex != NULL) \ g_strv_builder_add (_builder, _string); \ else \ { \ g_warning ("Blocklist file has an invalid " \ "regular expression '%s': %s", \ _string, local_error->message); \ g_clear_error (&local_error); \ } \ } \ } #define BUILD_REGEX_ESCAPED(_name, _builder) \ if (_name != NULL) \ { \ guint _n_strings = 0; \ \ _n_strings = g_list_model_get_n_items (_name); \ for (guint _i = 0; _i < _n_strings; _i++) \ { \ g_autoptr (GtkStringObject) _object = NULL; \ const char *_string = NULL; \ \ _object = g_list_model_get_item (_name, _i); \ _string = gtk_string_object_get_string (_object); \ \ g_strv_builder_take (_builder, g_regex_escape_string (_string, -1)); \ } \ } #define GATHER(name) \ if (name != NULL || \ name##_regex != NULL) \ { \ g_autoptr (GStrvBuilder) _builder = NULL; \ g_auto (GStrv) _patterns = NULL; \ \ _builder = g_strv_builder_new (); \ \ BUILD_REGEX_ESCAPED (name, _builder) \ BUILD_REGEX (name##_regex, _builder) \ \ _patterns = g_strv_builder_end (_builder); \ if (_patterns != NULL) \ { \ g_autofree char *_joined = NULL; \ g_autofree char *_regex_string = NULL; \ \ _joined = g_strjoinv ("|", _patterns); \ _regex_string = g_strdup_printf ("^(%s)$", _joined); \ data->name = g_regex_new (_regex_string, G_REGEX_OPTIMIZE, \ G_REGEX_MATCH_DEFAULT, NULL); \ } \ } GATHER (allow); GATHER (block); #undef GATHER #undef BUILD_REGEX_ESCAPED #undef BUILD_REGEX if (data->allow != NULL || data->block != NULL) g_ptr_array_add (regex_datas, g_steal_pointer (&data)); } } g_ptr_array_insert (self->blocklist_regexes, position + i, g_steal_pointer (®ex_datas)); } gtk_filter_changed (GTK_FILTER (self->group_filter), GTK_FILTER_CHANGE_DIFFERENT); gtk_filter_changed (GTK_FILTER (self->appid_filter), GTK_FILTER_CHANGE_DIFFERENT); } static void txt_blocklists_changed (BzApplication *self, guint position, guint removed, guint added, GListModel *model) { if (removed > 0) g_ptr_array_remove_range (self->txt_blocked_id_sets, position, removed); for (guint i = 0; i < added; i++) { g_autoptr (BzHashTableObject) obj = NULL; GHashTable *set = NULL; obj = g_list_model_get_item (model, position + i); set = bz_hash_table_object_get_hash_table (obj); g_ptr_array_insert (self->txt_blocked_id_sets, position + i, g_hash_table_ref (set)); } gtk_filter_changed (GTK_FILTER (self->group_filter), GTK_FILTER_CHANGE_DIFFERENT); gtk_filter_changed (GTK_FILTER (self->appid_filter), GTK_FILTER_CHANGE_DIFFERENT); } static void init_service_struct (BzApplication *self, GtkStringList *blocklists, GtkStringList *txt_blocklists, GtkStringList *curated_configs) { g_autoptr (GError) local_error = NULL; g_autoptr (GBytes) internal_config_bytes = NULL; g_autoptr (BzYamlParser) internal_config_parser = NULL; g_autoptr (GHashTable) internal_config_parse_results = NULL; const char *app_id = NULL; #ifdef HARDCODED_MAIN_CONFIG g_autoptr (GFile) config_file = NULL; g_autoptr (GBytes) config_bytes = NULL; #endif GtkCustomFilter *filter = NULL; GNetworkMonitor *network = NULL; g_type_ensure (BZ_TYPE_INTERNAL_CONFIG); internal_config_bytes = g_resources_lookup_data ( "/io/github/kolunmi/Bazaar/internal-config.yaml", G_RESOURCE_LOOKUP_FLAGS_NONE, NULL); g_assert (internal_config_bytes != NULL); internal_config_parser = bz_yaml_parser_new_for_resource_schema ( "/io/github/kolunmi/Bazaar/internal-config-schema.xml"); g_assert (internal_config_parser != NULL); internal_config_parse_results = bz_parser_process_bytes ( BZ_PARSER (internal_config_parser), internal_config_bytes, &local_error); if (internal_config_parse_results == NULL) g_critical ("FATAL: unable to parse internal config resource: %s", local_error->message); g_assert (internal_config_parse_results != NULL); self->internal_config = g_value_dup_object (g_hash_table_lookup (internal_config_parse_results, "/")); g_type_ensure (BZ_TYPE_MAIN_CONFIG); #ifdef HARDCODED_MAIN_CONFIG config_file = g_file_new_for_path (HARDCODED_MAIN_CONFIG); config_bytes = g_file_load_bytes (config_file, NULL, NULL, &local_error); if (config_bytes != NULL) { g_autoptr (BzYamlParser) parser = NULL; g_autoptr (GHashTable) parse_results = NULL; parser = bz_yaml_parser_new_for_resource_schema ( "/io/github/kolunmi/Bazaar/main-config-schema.xml"); parse_results = bz_parser_process_bytes ( BZ_PARSER (parser), config_bytes, &local_error); if (parse_results != NULL) { GListModel *override_eol_markings = NULL; self->config = g_value_dup_object (g_hash_table_lookup (parse_results, "/")); override_eol_markings = bz_main_config_get_override_eol_markings (self->config); if (override_eol_markings != NULL) { guint n_appids = 0; self->ignore_eol_set = g_hash_table_new_full ( g_str_hash, g_str_equal, g_free, g_free); n_appids = g_list_model_get_n_items (override_eol_markings); for (guint i = 0; i < n_appids; i++) { g_autoptr (GtkStringObject) string = NULL; const char *value = NULL; string = g_list_model_get_item (override_eol_markings, i); value = gtk_string_object_get_string (string); g_hash_table_replace (self->ignore_eol_set, g_strdup (value), NULL); } } } else { g_warning ("Could not load main config at %s: %s", HARDCODED_MAIN_CONFIG, local_error->message); g_clear_error (&local_error); } } g_clear_error (&local_error); #endif self->init_timer = g_timer_new (); self->ready_to_open_files = dex_promise_new (); self->first_window_opened = dex_promise_new (); if (self->config != NULL && bz_main_config_get_yaml_blocklist_paths (self->config) != NULL) { GListModel *paths = NULL; guint n_paths = 0; paths = bz_main_config_get_yaml_blocklist_paths (self->config); n_paths = g_list_model_get_n_items (paths); for (guint i = 0; i < n_paths; i++) { g_autoptr (GtkStringObject) string = NULL; string = g_list_model_get_item (paths, i); gtk_string_list_append (blocklists, gtk_string_object_get_string (string)); } } self->blocklists = g_object_ref (blocklists); self->blocklists_to_files = gtk_map_list_model_new ( NULL, (GtkMapListModelMapFunc) map_strings_to_files, NULL, NULL); gtk_map_list_model_set_model ( self->blocklists_to_files, G_LIST_MODEL (self->blocklists)); if (self->config != NULL && bz_main_config_get_txt_blocklist_paths (self->config) != NULL) { GListModel *paths = NULL; guint n_paths = 0; paths = bz_main_config_get_txt_blocklist_paths (self->config); n_paths = g_list_model_get_n_items (paths); for (guint i = 0; i < n_paths; i++) { g_autoptr (GtkStringObject) string = NULL; string = g_list_model_get_item (paths, i); gtk_string_list_append (txt_blocklists, gtk_string_object_get_string (string)); } } self->txt_blocklists = g_object_ref (txt_blocklists); self->txt_blocklists_to_files = gtk_map_list_model_new ( NULL, (GtkMapListModelMapFunc) map_strings_to_files, NULL, NULL); gtk_map_list_model_set_model ( self->txt_blocklists_to_files, G_LIST_MODEL (self->txt_blocklists)); if (self->config != NULL && bz_main_config_get_curated_config_paths (self->config) != NULL) { GListModel *paths = NULL; guint n_paths = 0; paths = bz_main_config_get_curated_config_paths (self->config); n_paths = g_list_model_get_n_items (paths); for (guint i = 0; i < n_paths; i++) { g_autoptr (GtkStringObject) string = NULL; string = g_list_model_get_item (paths, i); gtk_string_list_append (curated_configs, gtk_string_object_get_string (string)); } } #ifdef DEVELOPMENT_BUILD if (g_list_model_get_n_items (G_LIST_MODEL (curated_configs)) == 0) { g_autofree char *dest_path = NULL; g_autoptr (GFile) src = NULL; g_autoptr (GFile) dest = NULL; dest_path = g_build_filename (g_get_user_data_dir (), "example.yaml", NULL); src = g_file_new_for_path (DEVELOPMENT_EXAMPLE_YAML); dest = g_file_new_for_path (dest_path); g_file_copy (src, dest, G_FILE_COPY_OVERWRITE, NULL, NULL, NULL, NULL); gtk_string_list_append (curated_configs, dest_path); } #endif self->curated_configs = g_object_ref (curated_configs); self->curated_configs_to_files = gtk_map_list_model_new ( NULL, (GtkMapListModelMapFunc) map_strings_to_files, NULL, NULL); gtk_map_list_model_set_model ( self->curated_configs_to_files, G_LIST_MODEL (self->curated_configs)); self->search_biases = gtk_flatten_list_model_new (NULL); self->search_biases_backing = g_list_store_new (G_TYPE_LIST_MODEL); { GListModel *main_config_search_biases = NULL; GListModel *internal_config_search_biases = NULL; if (self->config != NULL) main_config_search_biases = bz_main_config_get_search_biases (self->config); internal_config_search_biases = bz_internal_config_get_search_biases (self->internal_config); /* Main config biases take precedence over the hardcoded ones */ if (main_config_search_biases != NULL) g_list_store_append (self->search_biases_backing, main_config_search_biases); if (internal_config_search_biases != NULL) g_list_store_append (self->search_biases_backing, internal_config_search_biases); } gtk_flatten_list_model_set_model ( self->search_biases, G_LIST_MODEL (self->search_biases_backing)); g_type_ensure (BZ_TYPE_ROOT_BLOCKLIST); g_type_ensure (BZ_TYPE_BLOCKLIST); g_type_ensure (BZ_TYPE_BLOCKLIST_CONDITION); self->blocklist_parser = bz_yaml_parser_new_for_resource_schema ( "/io/github/kolunmi/Bazaar/blocklist-schema.xml"); self->txt_blocklist_parser = bz_newline_parser_new ( TRUE, MAX_IDS_PER_BLOCKLIST); g_type_ensure (BZ_TYPE_ROOT_CURATED_CONFIG); g_type_ensure (BZ_TYPE_CURATED_ROW); g_type_ensure (BZ_TYPE_CURATED_SECTION); g_type_ensure (BZ_TYPE_CURATED_ARTICLE); self->curated_parser = bz_yaml_parser_new_for_resource_schema ( "/io/github/kolunmi/Bazaar/curated-config-schema.xml"); self->cache = bz_entry_cache_manager_new (); self->state = bz_state_info_new (); bz_state_info_set_busy (self->state, TRUE); bz_state_info_set_donation_prompt_dismissed (self->state, TRUE); bz_state_info_set_parental_age_rating (self->state, -1); state_info_set_icon_themes (self->state); { g_autoptr (GError) bus_error = NULL; g_autoptr (GDBusConnection) sys_bus = NULL; sys_bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &bus_error); if (sys_bus != NULL) self->malcontent = bz_malcontent_service_new (sys_bus, self->state); else g_warning ("Failed to connect to system bus for malcontent: %s", bus_error->message); if (self->malcontent != NULL) g_signal_connect_swapped ( self->state, "notify::parental-age-rating", G_CALLBACK (show_hide_app_setting_changed), self); } g_signal_connect_swapped ( self->state, "notify::disable-blocklists", G_CALLBACK (disable_blocklists_changed), self); network = g_network_monitor_get_default (); if (network != NULL) { GNetworkConnectivity connectivity = 0; gboolean metered = FALSE; connectivity = g_network_monitor_get_connectivity (network); bz_state_info_set_have_connection (self->state, connectivity == G_NETWORK_CONNECTIVITY_FULL); metered = g_network_monitor_get_network_metered (network); bz_state_info_set_metered_connection (self->state, metered); g_signal_connect_swapped (network, "notify", G_CALLBACK (network_status_changed), self); } else g_warning ("Unable to detect networking device! Continuing anyway..."); app_id = g_application_get_application_id (G_APPLICATION (self)); g_assert (app_id != NULL); g_debug ("Constructing gsettings for %s ...", app_id); self->settings = g_settings_new (app_id); if (g_settings_get_boolean (self->settings, "force-adwaita-icons")) { GtkSettings *gtk_settings = NULL; gtk_settings = gtk_settings_get_default (); g_object_set ( gtk_settings, "gtk-icon-theme-name", "Adwaita", NULL); } bz_state_info_set_hide_eol ( self->state, g_settings_get_boolean (self->settings, "hide-eol")); g_signal_connect_swapped ( self->settings, "changed::hide-eol", G_CALLBACK (show_hide_app_setting_changed), self); bz_state_info_set_show_only_foss ( self->state, g_settings_get_boolean (self->settings, "show-only-foss")); g_signal_connect_swapped ( self->settings, "changed::show-only-foss", G_CALLBACK (show_hide_app_setting_changed), self); bz_state_info_set_show_only_flathub ( self->state, g_settings_get_boolean (self->settings, "show-only-flathub")); g_signal_connect_swapped ( self->settings, "changed::show-only-flathub", G_CALLBACK (show_hide_app_setting_changed), self); bz_state_info_set_show_only_verified ( self->state, g_settings_get_boolean (self->settings, "show-only-verified")); g_signal_connect_swapped ( self->settings, "changed::show-only-verified", G_CALLBACK (show_hide_app_setting_changed), self); self->blocklist_regexes = g_ptr_array_new_with_free_func ( (GDestroyNotify) g_ptr_array_unref); self->blocklists_provider = bz_content_provider_new (); bz_content_provider_set_parser (self->blocklists_provider, BZ_PARSER (self->blocklist_parser)); bz_content_provider_set_input_files ( self->blocklists_provider, G_LIST_MODEL (self->blocklists_to_files)); g_signal_connect_swapped (self->blocklists_provider, "items-changed", G_CALLBACK (blocklists_changed), self); self->txt_blocked_id_sets = g_ptr_array_new_with_free_func ( (GDestroyNotify) g_hash_table_unref); self->txt_blocklists_provider = bz_content_provider_new (); bz_content_provider_set_parser (self->txt_blocklists_provider, BZ_PARSER (self->txt_blocklist_parser)); bz_content_provider_set_input_files ( self->txt_blocklists_provider, G_LIST_MODEL (self->txt_blocklists_to_files)); g_signal_connect_swapped (self->txt_blocklists_provider, "items-changed", G_CALLBACK (txt_blocklists_changed), self); self->groups = g_list_store_new (BZ_TYPE_ENTRY_GROUP); self->installed_apps = g_list_store_new (BZ_TYPE_ENTRY_GROUP); self->ids_to_groups = g_hash_table_new_full ( g_str_hash, g_str_equal, g_free, g_object_unref); self->eol_runtimes = g_hash_table_new_full ( g_str_hash, g_str_equal, g_free, g_free); self->sys_name_to_addons = g_hash_table_new_full ( g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_ptr_array_unref); self->usr_name_to_addons = g_hash_table_new_full ( g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_ptr_array_unref); self->sys_ref_to_addon_group_ids = g_hash_table_new_full ( g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_ptr_array_unref); self->usr_ref_to_addon_group_ids = g_hash_table_new_full ( g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_ptr_array_unref); self->entry_factory = bz_application_map_factory_new ( (GtkMapListModelMapFunc) map_ids_to_entries, self, NULL, NULL, NULL); filter = gtk_custom_filter_new ( (GtkCustomFilterFunc) filter_application_ids, self, NULL); self->appid_filter = g_object_ref_sink (g_steal_pointer (&filter)); self->application_factory = bz_application_map_factory_new ( (GtkMapListModelMapFunc) map_generic_ids_to_groups, self, NULL, NULL, GTK_FILTER (self->appid_filter)); filter = gtk_custom_filter_new ( (GtkCustomFilterFunc) filter_entry_groups, self, NULL); self->group_filter = g_object_ref_sink (g_steal_pointer (&filter)); self->group_filter_model = gtk_filter_list_model_new ( g_object_ref (G_LIST_MODEL (self->groups)), g_object_ref (GTK_FILTER (self->group_filter))); self->search_engine = bz_search_engine_new (); bz_search_engine_set_model (self->search_engine, G_LIST_MODEL (self->group_filter_model)); bz_search_engine_set_biases (self->search_engine, G_LIST_MODEL (self->search_biases)); self->curated_provider = bz_content_provider_new (); bz_content_provider_set_input_files ( self->curated_provider, G_LIST_MODEL (self->curated_configs_to_files)); bz_content_provider_set_parser (self->curated_provider, BZ_PARSER (self->curated_parser)); self->transactions = bz_transaction_manager_new (); bz_transaction_manager_set_config (self->transactions, self->config); g_signal_connect_swapped ( self->transactions, "notify::active", G_CALLBACK (transactions_active_changed), self); bz_state_info_set_all_entry_groups (self->state, G_LIST_MODEL (self->groups)); bz_state_info_set_all_installed_entry_groups (self->state, G_LIST_MODEL (self->installed_apps)); bz_state_info_set_filtered_entry_groups (self->state, G_LIST_MODEL (self->group_filter_model)); bz_state_info_set_application_factory (self->state, self->application_factory); bz_state_info_set_blocklists (self->state, G_LIST_MODEL (self->blocklists)); bz_state_info_set_blocklists_provider (self->state, self->blocklists_provider); bz_state_info_set_curated_configs (self->state, G_LIST_MODEL (self->curated_configs)); bz_state_info_set_curated_provider (self->state, self->curated_provider); bz_state_info_set_entry_factory (self->state, self->entry_factory); bz_state_info_set_main_config (self->state, self->config); bz_state_info_set_search_engine (self->state, self->search_engine); bz_state_info_set_settings (self->state, self->settings); bz_state_info_set_transaction_manager (self->state, self->transactions); bz_state_info_set_txt_blocklists (self->state, G_LIST_MODEL (self->txt_blocklists)); bz_state_info_set_txt_blocklists_provider (self->state, self->txt_blocklists_provider); bz_state_info_set_cache_manager (self->state, self->cache); g_object_bind_property ( self->state, "allow-manual-sync", g_action_map_lookup_action (G_ACTION_MAP (self), "sync-remotes"), "enabled", G_BINDING_SYNC_CREATE); gtk_style_context_add_provider_for_display ( gdk_display_get_default (), bz_get_pride_style_provider (), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); } static GtkWindow * get_or_create_window (BzApplication *self) { GtkWindow *window = NULL; GList *windows = NULL; window = gtk_application_get_active_window (GTK_APPLICATION (self)); if (BZ_IS_WINDOW (window)) { if (adw_application_window_get_visible_dialog (ADW_APPLICATION_WINDOW (window)) != NULL) return new_window (self); gtk_window_present (window); return window; } windows = gtk_application_get_windows (GTK_APPLICATION (self)); for (GList *l = windows; l != NULL; l = l->next) { if (BZ_IS_WINDOW (l->data)) { if (adw_application_window_get_visible_dialog (ADW_APPLICATION_WINDOW (l->data)) != NULL) continue; gtk_window_present (GTK_WINDOW (l->data)); return GTK_WINDOW (l->data); } } return new_window (self); } static GtkWindow * new_window (BzApplication *self) { BzWindow *window = NULL; g_autoptr (GtkWidget) main_window = NULL; int width = 0; int height = 0; window = bz_window_new (self->state); gtk_application_add_window (GTK_APPLICATION (self), GTK_WINDOW (window)); main_window = g_weak_ref_get (&self->main_window); if (main_window != NULL) { width = gtk_widget_get_width (main_window); height = gtk_widget_get_height (main_window); g_settings_set (self->settings, "window-dimensions", "(ii)", width, height); } else { g_settings_get (self->settings, "window-dimensions", "(ii)", &width, &height); g_signal_connect_object ( window, "close-request", G_CALLBACK (window_close_request), self, G_CONNECT_SWAPPED); g_weak_ref_init (&self->main_window, window); } gtk_window_set_default_size (GTK_WINDOW (window), width, height); gtk_window_present (GTK_WINDOW (window)); if (dex_future_is_pending (DEX_FUTURE (self->first_window_opened))) dex_promise_resolve_boolean (self->first_window_opened, TRUE); return GTK_WINDOW (window); } static void open_appstream_take (BzApplication *self, char *appstream) { g_autoptr (BzWeakRef) wr = NULL; const char *id = NULL; g_info ("Loading appstream link %s...", appstream); wr = bz_weak_ref_new (self); if (g_str_has_prefix (appstream, "appstream://")) id = appstream + strlen ("appstream://"); else id = appstream + strlen ("appstream:"); dex_future_disown (dex_scheduler_spawnv ( dex_scheduler_get_default (), bz_get_dex_stack_size (), G_CALLBACK (open_appstream_fiber), 2, BZ_TYPE_WEAK_REF, wr, G_TYPE_STRING, id)); g_free (appstream); } static void open_flatpakref_take (BzApplication *self, GFile *file) { g_autoptr (GError) local_error = NULL; g_autoptr (BzWeakRef) wr = NULL; gboolean result = FALSE; g_autofree char *path = NULL; wr = bz_weak_ref_new (self); path = g_file_get_path (file); 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); } } dex_future_disown (dex_scheduler_spawnv ( dex_scheduler_get_default (), bz_get_dex_stack_size (), G_CALLBACK (open_flatpakref_fiber), 2, BZ_TYPE_WEAK_REF, wr, G_TYPE_FILE, file)); g_object_unref (file); } static void open_metainfo_take (BzApplication *self, GFile *file) { g_autoptr (BzWeakRef) wr = NULL; wr = bz_weak_ref_new (self); dex_future_disown (dex_scheduler_spawnv ( dex_scheduler_get_default (), bz_get_dex_stack_size (), G_CALLBACK (open_metainfo_fiber), 2, BZ_TYPE_WEAK_REF, wr, G_TYPE_FILE, file)); g_object_unref (file); } static void open_location_take (BzApplication *self, const char *name_for_suffix_check, GFile *file) { if (name_for_suffix_check != NULL && (g_str_has_suffix (name_for_suffix_check, ".metainfo.xml") || g_str_has_suffix (name_for_suffix_check, ".appdata.xml"))) open_metainfo_take (self, file); else open_flatpakref_take (self, file); } static void command_line_open_location (BzApplication *self, GApplicationCommandLine *cmdline, const char *location) { g_autoptr (GFile) file = NULL; if (g_uri_is_valid (location, G_URI_FLAGS_NONE, NULL) && g_str_has_prefix (location, "appstream:")) { open_appstream_take (self, g_strdup (location)); return; } if (g_uri_is_valid (location, G_URI_FLAGS_NONE, NULL)) file = g_file_new_for_uri (location); else if (g_path_is_absolute (location)) file = g_file_new_for_path (location); else { const char *cwd = NULL; cwd = g_application_command_line_get_cwd (cmdline); file = cwd != NULL ? g_file_new_build_filename (cwd, location, NULL) : g_file_new_for_path (location); } open_location_take (self, location, g_steal_pointer (&file)); } static void open_generic_id (BzApplication *self, const char *generic_id, gboolean animate) { BzEntryGroup *group = NULL; GtkWindow *window = NULL; g_autofree char *corrected_id = NULL; const char *original_id = generic_id; const char *matched_id = generic_id; gboolean case_fixed = FALSE; group = g_hash_table_lookup (self->ids_to_groups, generic_id); // This is needed because KDE likes to mangle IDs just for fun... if (group == NULL) { gsize len = 0; len = strlen (generic_id); // if it has more than 3 parts and end with ".desktop" then cut it off. if (len > 8 && g_str_has_suffix (generic_id, ".desktop")) { guint n_dots = 0; const char *suffix = NULL; for (const char *p = strchr (generic_id, '.'); p != NULL; p = strchr (p, '.')) { if (++n_dots >= 3) { suffix = strstr (generic_id, ".desktop"); g_assert (suffix != NULL); break; } } if (suffix != NULL) { corrected_id = g_strndup (generic_id, suffix - generic_id); generic_id = corrected_id; matched_id = corrected_id; group = g_hash_table_lookup (self->ids_to_groups, generic_id); } } if (group == NULL) { GHashTableIter iter = { 0 }; g_hash_table_iter_init (&iter, self->ids_to_groups); for (;;) /* screeching sounds */ { char *key = NULL; BzEntryGroup *value = NULL; if (!g_hash_table_iter_next ( &iter, (gpointer *) &key, (gpointer *) &value)) break; if (g_ascii_strcasecmp (key, generic_id) == 0) { group = value; matched_id = key; case_fixed = TRUE; break; } } } if (group == NULL) matched_id = original_id; } window = get_or_create_window (self); if (group != NULL) { gtk_widget_activate_action (GTK_WIDGET (window), "window.show-group", "(sb)", matched_id, animate); } else { g_autofree char *message = NULL; message = g_strdup_printf ("ID '%s' was not found", original_id); bz_show_error_for_widget (GTK_WIDGET (window), _ ("Could not find app"), message); } } static gpointer map_strings_to_files (GtkStringObject *string, gpointer data) { const char *path = NULL; GFile *result = NULL; path = gtk_string_object_get_string (string); result = g_file_new_for_path (path); g_object_unref (string); return result; } static gpointer map_generic_ids_to_groups (GtkStringObject *string, BzApplication *self) { BzEntryGroup *group = NULL; group = g_hash_table_lookup ( self->ids_to_groups, gtk_string_object_get_string (string)); g_object_unref (string); return bz_object_maybe_ref (group); } static gpointer map_ids_to_entries (GtkStringObject *string, BzApplication *self) { g_autoptr (GError) local_error = NULL; const char *id = NULL; g_autoptr (DexFuture) future = NULL; g_autoptr (BzResult) result = NULL; id = gtk_string_object_get_string (string); future = bz_entry_cache_manager_get (self->cache, id); result = bz_result_new (future); g_object_unref (string); return g_steal_pointer (&result); } static gboolean filter_application_ids (GtkStringObject *string, BzApplication *self) { BzEntryGroup *group = NULL; group = g_hash_table_lookup ( self->ids_to_groups, gtk_string_object_get_string (string)); if (group != NULL) return validate_group_for_ui (self, group); else return FALSE; } static gboolean filter_entry_groups (BzEntryGroup *group, BzApplication *self) { return validate_group_for_ui (self, group); } static gint cmp_group (BzEntryGroup *a, BzEntryGroup *b, gpointer user_data) { const char *title_a = NULL; const char *title_b = NULL; title_a = bz_entry_group_get_title (a); title_b = bz_entry_group_get_title (b); if (title_a == NULL) return 1; if (title_b == NULL) return -1; return strcasecmp (title_a, title_b); } static gboolean validate_group_for_ui (BzApplication *self, BzEntryGroup *group) { const char *id = NULL; int allowed_priority = G_MAXINT; int blocked_priority = G_MAXINT; if (bz_state_info_get_hide_eol (self->state) && bz_entry_group_get_eol (group) != NULL) return FALSE; if (bz_state_info_get_show_only_foss (self->state) && !bz_entry_group_get_is_floss (group)) return FALSE; if (bz_state_info_get_show_only_flathub (self->state) && !bz_entry_group_get_is_flathub (group)) return FALSE; if (bz_state_info_get_show_only_verified (self->state) && !bz_entry_group_get_is_verified (group)) return FALSE; if (self->malcontent != NULL) { int parental_age = -1; int app_age = 0; parental_age = bz_state_info_get_parental_age_rating (self->state); app_age = bz_entry_group_get_content_age_rating (group); if (app_age > parental_age && parental_age != -1) return FALSE; } if (bz_state_info_get_disable_blocklists (self->state)) return TRUE; id = bz_entry_group_get_id (group); for (guint i = 0; i < self->txt_blocked_id_sets->len; i++) { GHashTable *set = NULL; set = g_ptr_array_index (self->txt_blocked_id_sets, i); if (g_hash_table_contains (set, id)) return FALSE; } for (guint i = 0; i < self->blocklist_regexes->len; i++) { GPtrArray *regex_datas = NULL; regex_datas = g_ptr_array_index (self->blocklist_regexes, i); for (guint j = 0; j < regex_datas->len; j++) { BlocklistRegexData *data = NULL; data = g_ptr_array_index (regex_datas, j); if (data->allow != NULL && data->priority < allowed_priority && g_regex_match (data->allow, id, G_REGEX_MATCH_DEFAULT, NULL)) allowed_priority = data->priority; if (data->block != NULL && data->priority < blocked_priority && g_regex_match (data->block, id, G_REGEX_MATCH_DEFAULT, NULL)) blocked_priority = data->priority; } } return allowed_priority <= blocked_priority; } static DexFuture * make_sync_future (BzApplication *self) { g_autoptr (GError) local_error = NULL; g_autoptr (GSubprocess) refresh_worker = NULL; g_autoptr (DexFuture) backend_future = NULL; g_autoptr (DexFuture) flathub_future = NULL; g_autoptr (DexFuture) ret_future = NULL; bz_state_info_set_allow_manual_sync (self->state, FALSE); bz_state_info_set_syncing (self->state, TRUE); bz_state_info_set_recently_synced (self->state, TRUE); finish_with_background_task_label (self); refresh_worker = g_subprocess_new ( G_SUBPROCESS_FLAGS_NONE, &local_error, BAZAAR_BIN_NAME, REFRESH_WORKER_CLI_OPTION, NULL); if (refresh_worker == NULL) g_critical ("FATAL!!! The refresh worker could not be spawned: %s", local_error->message); g_assert (refresh_worker != NULL); backend_future = dex_subprocess_wait_check (refresh_worker); backend_future = dex_future_finally ( backend_future, (DexFutureCallback) backend_sync_finally, bz_weak_ref_new (self), (GDestroyNotify) bz_weak_ref_unref); g_clear_object (&self->tmp_flathub); self->tmp_flathub = bz_flathub_state_new (); flathub_future = bz_flathub_state_update_to_today (self->tmp_flathub); flathub_future = dex_future_finally ( flathub_future, (DexFutureCallback) flathub_update_finally, bz_weak_ref_new (self), (GDestroyNotify) bz_weak_ref_unref); ret_future = dex_future_all ( dex_ref (backend_future), dex_ref (flathub_future), NULL); ret_future = dex_future_finally ( ret_future, (DexFutureCallback) sync_finally, bz_weak_ref_new (self), (GDestroyNotify) bz_weak_ref_unref); return g_steal_pointer (&ret_future); } static void finish_with_background_task_label (BzApplication *self) { if (self->n_entries_incoming > 0) bz_state_info_set_background_task_label_take_printf ( self->state, ngettext ("Loading %d app…", "Loading %d apps…", self->n_entries_incoming), self->n_entries_incoming); else if (bz_state_info_get_syncing (self->state)) bz_state_info_set_background_task_label (self->state, _ ("Refreshing…")); else if (bz_state_info_get_busy (self->state)) bz_state_info_set_background_task_label (self->state, _ ("Writing to cache…")); else bz_state_info_set_background_task_label (self->state, NULL); } static void state_info_set_icon_themes (BzStateInfo *state) { g_autoptr (GtkIconTheme) user_theme = NULL; g_autoptr (GtkIconTheme) system_theme = NULL; g_autofree char *user_export_dir = NULL; user_theme = gtk_icon_theme_new (); user_export_dir = g_build_filename (g_get_home_dir (), ".local/share/flatpak/exports/share/icons", NULL); gtk_icon_theme_add_search_path (user_theme, user_export_dir); bz_state_info_set_user_icon_theme (state, user_theme); system_theme = gtk_icon_theme_new (); gtk_icon_theme_add_search_path (system_theme, "/var/lib/flatpak/exports/share/icons"); bz_state_info_set_system_icon_theme (state, system_theme); }