summaryrefslogtreecommitdiffstats
path: root/lib/gs-app-query.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/gs-app-query.c')
-rw-r--r--lib/gs-app-query.c1173
1 files changed, 1173 insertions, 0 deletions
diff --git a/lib/gs-app-query.c b/lib/gs-app-query.c
new file mode 100644
index 0000000..cd2fc93
--- /dev/null
+++ b/lib/gs-app-query.c
@@ -0,0 +1,1173 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ * vi:set noexpandtab tabstop=8 shiftwidth=8:
+ *
+ * Copyright (C) 2022 Endless OS Foundation LLC
+ *
+ * Author: Philip Withnall <pwithnall@endlessos.org>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+/**
+ * SECTION:gs-app-query
+ * @short_description: Immutable representation of a query for apps
+ *
+ * #GsAppQuery is an object to represent a query for apps.
+ *
+ * It will typically be used with #GsPluginJobListApps, which searches for
+ * matching apps, but it may have multiple consumers. #GsAppQuery only
+ * represents the query and does not provide an implementation for executing
+ * that query.
+ *
+ * It is immutable after construction, and hence threadsafe. It may be extended
+ * in future by adding more query properties. The existing query properties are
+ * conjunctive: results should only be returned which match *all* properties
+ * which are set, not _any_ properties which are set.
+ *
+ * The set of apps returned for the query can be controlled with the
+ * #GsAppQuery:refine-flags,
+ * #GsAppQuery:max-results and
+ * #GsAppQuery:dedupe-flags properties. If `refine-flags` is
+ * set, all results must be refined using the given set of refine flags (see
+ * #GsPluginJobRefine). `max-results` and `dedupe-flags` are used to limit the
+ * set of results.
+ *
+ * Results must always be processed in this order:
+ * - Filtering using #GsAppQuery:filter-func (and any other custom filter
+ * functions the query executor provides).
+ * - Deduplication using #GsAppQuery:dedupe-flags.
+ * - Sorting using #GsAppQuery:sort-func.
+ * - Truncating result list length to #GsAppQuery:max-results.
+ *
+ * Since: 43
+ */
+
+#include "config.h"
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include "gs-app.h"
+#include "gs-app-list.h"
+#include "gs-app-query.h"
+#include "gs-enums.h"
+#include "gs-plugin-types.h"
+#include "gs-utils.h"
+
+struct _GsAppQuery
+{
+ GObject parent;
+
+ GsPluginRefineFlags refine_flags;
+ guint max_results;
+ GsAppListFilterFlags dedupe_flags;
+
+ GsAppListSortFunc sort_func;
+ gpointer sort_user_data;
+ GDestroyNotify sort_user_data_notify;
+
+ GsAppListFilterFunc filter_func;
+ gpointer filter_user_data;
+ GDestroyNotify filter_user_data_notify;
+
+ /* This is guaranteed to either be %NULL, or a non-empty array */
+ gchar **provides_files; /* (owned) (nullable) (array zero-terminated=1) */
+ GDateTime *released_since; /* (owned) (nullable) */
+ GsAppQueryTristate is_curated;
+ GsAppQueryTristate is_featured;
+ GsCategory *category; /* (nullable) (owned) */
+ GsAppQueryTristate is_installed;
+
+ /* This is guaranteed to either be %NULL, or a non-empty array */
+ gchar **deployment_featured; /* (owned) (nullable) (array zero-terminated=1) */
+ /* This is guaranteed to either be %NULL, or a non-empty array */
+ gchar **developers; /* (owned) (nullable) (array zero-terminated=1) */
+
+ gchar **keywords; /* (owned) (nullable) (array zero-terminated=1) */
+ GsApp *alternate_of; /* (nullable) (owned) */
+ gchar *provides_tag; /* (owned) (nullable) */
+ GsAppQueryProvidesType provides_type;
+};
+
+G_DEFINE_TYPE (GsAppQuery, gs_app_query, G_TYPE_OBJECT)
+
+typedef enum {
+ PROP_REFINE_FLAGS = 1,
+ PROP_MAX_RESULTS,
+ PROP_DEDUPE_FLAGS,
+ PROP_SORT_FUNC,
+ PROP_SORT_USER_DATA,
+ PROP_SORT_USER_DATA_NOTIFY,
+ PROP_FILTER_FUNC,
+ PROP_FILTER_USER_DATA,
+ PROP_FILTER_USER_DATA_NOTIFY,
+ PROP_DEPLOYMENT_FEATURED,
+ PROP_DEVELOPERS,
+ PROP_PROVIDES_FILES,
+ PROP_RELEASED_SINCE,
+ PROP_IS_CURATED,
+ PROP_IS_FEATURED,
+ PROP_CATEGORY,
+ PROP_IS_INSTALLED,
+ PROP_KEYWORDS,
+ PROP_ALTERNATE_OF,
+ PROP_PROVIDES_TAG,
+ PROP_PROVIDES_TYPE,
+} GsAppQueryProperty;
+
+static GParamSpec *props[PROP_PROVIDES_TYPE + 1] = { NULL, };
+
+static void
+gs_app_query_constructed (GObject *object)
+{
+ GsAppQuery *self = GS_APP_QUERY (object);
+
+ G_OBJECT_CLASS (gs_app_query_parent_class)->constructed (object);
+
+ g_assert ((self->provides_tag != NULL) == (self->provides_type != GS_APP_QUERY_PROVIDES_UNKNOWN));
+}
+
+static void
+gs_app_query_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GsAppQuery *self = GS_APP_QUERY (object);
+
+ switch ((GsAppQueryProperty) prop_id) {
+ case PROP_REFINE_FLAGS:
+ g_value_set_flags (value, self->refine_flags);
+ break;
+ case PROP_MAX_RESULTS:
+ g_value_set_uint (value, self->max_results);
+ break;
+ case PROP_DEDUPE_FLAGS:
+ g_value_set_flags (value, self->dedupe_flags);
+ break;
+ case PROP_SORT_FUNC:
+ g_value_set_pointer (value, self->sort_func);
+ break;
+ case PROP_SORT_USER_DATA:
+ g_value_set_pointer (value, self->sort_user_data);
+ break;
+ case PROP_SORT_USER_DATA_NOTIFY:
+ g_value_set_pointer (value, self->sort_user_data_notify);
+ break;
+ case PROP_FILTER_FUNC:
+ g_value_set_pointer (value, self->filter_func);
+ break;
+ case PROP_FILTER_USER_DATA:
+ g_value_set_pointer (value, self->filter_user_data);
+ break;
+ case PROP_FILTER_USER_DATA_NOTIFY:
+ g_value_set_pointer (value, self->filter_user_data_notify);
+ break;
+ case PROP_DEPLOYMENT_FEATURED:
+ g_value_set_boxed (value, self->deployment_featured);
+ break;
+ case PROP_DEVELOPERS:
+ g_value_set_boxed (value, self->developers);
+ break;
+ case PROP_PROVIDES_FILES:
+ g_value_set_boxed (value, self->provides_files);
+ break;
+ case PROP_RELEASED_SINCE:
+ g_value_set_boxed (value, self->released_since);
+ break;
+ case PROP_IS_CURATED:
+ g_value_set_enum (value, self->is_curated);
+ break;
+ case PROP_IS_FEATURED:
+ g_value_set_enum (value, self->is_featured);
+ break;
+ case PROP_CATEGORY:
+ g_value_set_object (value, self->category);
+ break;
+ case PROP_IS_INSTALLED:
+ g_value_set_enum (value, self->is_installed);
+ break;
+ case PROP_KEYWORDS:
+ g_value_set_boxed (value, self->keywords);
+ break;
+ case PROP_ALTERNATE_OF:
+ g_value_set_object (value, self->alternate_of);
+ break;
+ case PROP_PROVIDES_TAG:
+ g_value_set_string (value, self->provides_tag);
+ break;
+ case PROP_PROVIDES_TYPE:
+ g_value_set_enum (value, self->provides_type);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gs_app_query_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GsAppQuery *self = GS_APP_QUERY (object);
+
+ switch ((GsAppQueryProperty) prop_id) {
+ case PROP_REFINE_FLAGS:
+ /* Construct only. */
+ g_assert (self->refine_flags == 0);
+ self->refine_flags = g_value_get_flags (value);
+ break;
+ case PROP_MAX_RESULTS:
+ /* Construct only. */
+ g_assert (self->max_results == 0);
+ self->max_results = g_value_get_uint (value);
+ break;
+ case PROP_DEDUPE_FLAGS:
+ /* Construct only. */
+ g_assert (self->dedupe_flags == 0);
+ self->dedupe_flags = g_value_get_flags (value);
+ break;
+ case PROP_SORT_FUNC:
+ /* Construct only. */
+ g_assert (self->sort_func == NULL);
+ self->sort_func = g_value_get_pointer (value);
+ break;
+ case PROP_SORT_USER_DATA:
+ /* Construct only. */
+ g_assert (self->sort_user_data == NULL);
+ self->sort_user_data = g_value_get_pointer (value);
+ break;
+ case PROP_SORT_USER_DATA_NOTIFY:
+ /* Construct only. */
+ g_assert (self->sort_user_data_notify == NULL);
+ self->sort_user_data_notify = g_value_get_pointer (value);
+ break;
+ case PROP_FILTER_FUNC:
+ /* Construct only. */
+ g_assert (self->filter_func == NULL);
+ self->filter_func = g_value_get_pointer (value);
+ break;
+ case PROP_FILTER_USER_DATA:
+ /* Construct only. */
+ g_assert (self->filter_user_data == NULL);
+ self->filter_user_data = g_value_get_pointer (value);
+ break;
+ case PROP_FILTER_USER_DATA_NOTIFY:
+ /* Construct only. */
+ g_assert (self->filter_user_data_notify == NULL);
+ self->filter_user_data_notify = g_value_get_pointer (value);
+ break;
+ case PROP_DEPLOYMENT_FEATURED:
+ /* Construct only. */
+ g_assert (self->deployment_featured == NULL);
+ self->deployment_featured = g_value_dup_boxed (value);
+
+ /* Squash empty arrays to %NULL. */
+ if (self->deployment_featured != NULL && self->deployment_featured[0] == NULL)
+ g_clear_pointer (&self->deployment_featured, g_strfreev);
+
+ break;
+ case PROP_DEVELOPERS:
+ /* Construct only. */
+ g_assert (self->developers == NULL);
+ self->developers = g_value_dup_boxed (value);
+
+ /* Squash empty arrays to %NULL. */
+ if (self->developers != NULL && self->developers[0] == NULL)
+ g_clear_pointer (&self->developers, g_strfreev);
+
+ break;
+ case PROP_PROVIDES_FILES:
+ /* Construct only. */
+ g_assert (self->provides_files == NULL);
+ self->provides_files = g_value_dup_boxed (value);
+
+ /* Squash empty arrays to %NULL. */
+ if (self->provides_files != NULL && self->provides_files[0] == NULL)
+ g_clear_pointer (&self->provides_files, g_strfreev);
+
+ break;
+ case PROP_RELEASED_SINCE:
+ /* Construct only. */
+ g_assert (self->released_since == NULL);
+ self->released_since = g_value_dup_boxed (value);
+ break;
+ case PROP_IS_CURATED:
+ /* Construct only. */
+ g_assert (self->is_curated == GS_APP_QUERY_TRISTATE_UNSET);
+ self->is_curated = g_value_get_enum (value);
+ break;
+ case PROP_IS_FEATURED:
+ /* Construct only. */
+ g_assert (self->is_featured == GS_APP_QUERY_TRISTATE_UNSET);
+ self->is_featured = g_value_get_enum (value);
+ break;
+ case PROP_CATEGORY:
+ /* Construct only. */
+ g_assert (self->category == NULL);
+ self->category = g_value_dup_object (value);
+ break;
+ case PROP_IS_INSTALLED:
+ /* Construct only. */
+ g_assert (self->is_installed == GS_APP_QUERY_TRISTATE_UNSET);
+ self->is_installed = g_value_get_enum (value);
+ break;
+ case PROP_KEYWORDS:
+ /* Construct only. */
+ g_assert (self->keywords == NULL);
+ self->keywords = g_value_dup_boxed (value);
+
+ /* Squash empty arrays to %NULL. */
+ if (self->keywords != NULL && self->keywords[0] == NULL)
+ g_clear_pointer (&self->keywords, g_strfreev);
+
+ break;
+ case PROP_ALTERNATE_OF:
+ /* Construct only. */
+ g_assert (self->alternate_of == NULL);
+ self->alternate_of = g_value_dup_object (value);
+ break;
+ case PROP_PROVIDES_TAG:
+ /* Construct only. */
+ g_assert (self->provides_tag == NULL);
+ self->provides_tag = g_value_dup_string (value);
+ break;
+ case PROP_PROVIDES_TYPE:
+ /* Construct only. */
+ g_assert (self->provides_type == GS_APP_QUERY_PROVIDES_UNKNOWN);
+ self->provides_type = g_value_get_enum (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gs_app_query_dispose (GObject *object)
+{
+ GsAppQuery *self = GS_APP_QUERY (object);
+
+ if (self->sort_user_data_notify != NULL && self->sort_user_data != NULL) {
+ self->sort_user_data_notify (g_steal_pointer (&self->sort_user_data));
+ self->sort_user_data_notify = NULL;
+ }
+
+ if (self->filter_user_data_notify != NULL && self->filter_user_data != NULL) {
+ self->filter_user_data_notify (g_steal_pointer (&self->filter_user_data));
+ self->filter_user_data_notify = NULL;
+ }
+
+ g_clear_object (&self->category);
+ g_clear_object (&self->alternate_of);
+
+ G_OBJECT_CLASS (gs_app_query_parent_class)->dispose (object);
+}
+
+static void
+gs_app_query_finalize (GObject *object)
+{
+ GsAppQuery *self = GS_APP_QUERY (object);
+
+ g_clear_pointer (&self->deployment_featured, g_strfreev);
+ g_clear_pointer (&self->developers, g_strfreev);
+ g_clear_pointer (&self->provides_files, g_strfreev);
+ g_clear_pointer (&self->released_since, g_date_time_unref);
+ g_clear_pointer (&self->keywords, g_strfreev);
+ g_clear_pointer (&self->provides_tag, g_free);
+
+ G_OBJECT_CLASS (gs_app_query_parent_class)->finalize (object);
+}
+
+static void
+gs_app_query_class_init (GsAppQueryClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->constructed = gs_app_query_constructed;
+ object_class->get_property = gs_app_query_get_property;
+ object_class->set_property = gs_app_query_set_property;
+ object_class->dispose = gs_app_query_dispose;
+ object_class->finalize = gs_app_query_finalize;
+
+ /**
+ * GsAppQuery:refine-flags:
+ *
+ * Flags to specify how the returned apps must be refined, if at all.
+ *
+ * Since: 43
+ */
+ props[PROP_REFINE_FLAGS] =
+ g_param_spec_flags ("refine-flags", "Refine Flags",
+ "Flags to specify how the returned apps must be refined, if at all.",
+ GS_TYPE_PLUGIN_REFINE_FLAGS, GS_PLUGIN_REFINE_FLAGS_NONE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * GsAppQuery:max-results:
+ *
+ * Maximum number of results to return, or 0 for no limit.
+ *
+ * Since: 43
+ */
+ props[PROP_MAX_RESULTS] =
+ g_param_spec_uint ("max-results", "Max Results",
+ "Maximum number of results to return, or 0 for no limit.",
+ 0, G_MAXUINT, 0,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * GsAppQuery:dedupe-flags:
+ *
+ * Flags to specify how to deduplicate the returned apps, if at all.
+ *
+ * Since: 43
+ */
+ props[PROP_DEDUPE_FLAGS] =
+ g_param_spec_flags ("dedupe-flags", "Dedupe Flags",
+ "Flags to specify how to deduplicate the returned apps, if at all.",
+ GS_TYPE_APP_LIST_FILTER_FLAGS, GS_APP_LIST_FILTER_FLAG_NONE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * GsAppQuery:sort-func: (nullable)
+ *
+ * A sort function to sort the returned apps.
+ *
+ * This must be of type #GsAppListSortFunc.
+ *
+ * Since: 43
+ */
+ props[PROP_SORT_FUNC] =
+ g_param_spec_pointer ("sort-func", "Sort Function",
+ "A sort function to sort the returned apps.",
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * GsAppQuery:sort-user-data: (nullable)
+ *
+ * User data to pass to #GsAppQuery:sort-func.
+ *
+ * Since: 43
+ */
+ props[PROP_SORT_USER_DATA] =
+ g_param_spec_pointer ("sort-user-data", "Sort User Data",
+ "User data to pass to #GsAppQuery:sort-func.",
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * GsAppQuery:sort-user-data-notify: (nullable)
+ *
+ * A function to free #GsAppQuery:sort-user-data once it is no longer
+ * needed.
+ *
+ * This must be of type #GDestroyNotify.
+ *
+ * This will be called exactly once between being set and when the
+ * #GsAppQuery is finalized.
+ *
+ * Since: 43
+ */
+ props[PROP_SORT_USER_DATA_NOTIFY] =
+ g_param_spec_pointer ("sort-user-data-notify", "Sort User Data Notify",
+ "A function to free #GsAppQuery:sort-user-data once it is no longer needed.",
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * GsAppQuery:filter-func: (nullable)
+ *
+ * A filter function to filter the returned apps.
+ *
+ * This must be of type #GsAppListFilterFunc.
+ *
+ * Since: 43
+ */
+ props[PROP_FILTER_FUNC] =
+ g_param_spec_pointer ("filter-func", "Filter Function",
+ "A filter function to filter the returned apps.",
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * GsAppQuery:filter-user-data: (nullable)
+ *
+ * User data to pass to #GsAppQuery:filter-func.
+ *
+ * Since: 43
+ */
+ props[PROP_FILTER_USER_DATA] =
+ g_param_spec_pointer ("filter-user-data", "Filter User Data",
+ "User data to pass to #GsAppQuery:filter-func.",
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * GsAppQuery:filter-user-data-notify: (nullable)
+ *
+ * A function to free #GsAppQuery:filter-user-data once it is no longer
+ * needed.
+ *
+ * This must be of type #GDestroyNotify.
+ *
+ * This will be called exactly once between being set and when the
+ * #GsAppQuery is finalized.
+ *
+ * Since: 43
+ */
+ props[PROP_FILTER_USER_DATA_NOTIFY] =
+ g_param_spec_pointer ("filter-user-data-notify", "Filter User Data Notify",
+ "A function to free #GsAppQuery:filter-user-data once it is no longer needed.",
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * GsAppQuery:deployment-featured: (nullable)
+ *
+ * A list of `GnomeSoftware::DeploymentFeatured` app keys.
+ *
+ * Search for applications that should be featured in a deployment-specific
+ * section on the overview page.
+ * This is expected to be a curated list of applications that are high quality
+ * and feature-complete. Only apps matching at least one of the keys in this
+ * list are returned.
+ *
+ * This may be %NULL to not filter on it. An empty array is
+ * considered equivalent to %NULL.
+ *
+ * Since: 43
+ */
+ props[PROP_DEPLOYMENT_FEATURED] =
+ g_param_spec_boxed ("deployment-featured", "Deployment Featured",
+ "A list of `GnomeSoftware::DeploymentFeatured` app keys.",
+ G_TYPE_STRV,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * GsAppQuery:developers: (nullable)
+ *
+ * A list of developers to search the apps for.
+ *
+ * Used to search for apps which are provided by given developer(s).
+ *
+ * This may be %NULL to not filter on by them. An empty array is
+ * considered equivalent to %NULL.
+ *
+ * Since: 43
+ */
+ props[PROP_DEVELOPERS] =
+ g_param_spec_boxed ("developers", "Developers",
+ "A list of developers who provide the apps.",
+ G_TYPE_STRV,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * GsAppQuery:provides-files: (nullable)
+ *
+ * A list of file paths which the apps must provide.
+ *
+ * Used to search for apps which provide specific files on the local
+ * file system.
+ *
+ * This may be %NULL to not filter on file paths. An empty array is
+ * considered equivalent to %NULL.
+ *
+ * Since: 43
+ */
+ props[PROP_PROVIDES_FILES] =
+ g_param_spec_boxed ("provides-files", "Provides Files",
+ "A list of file paths which the apps must provide.",
+ G_TYPE_STRV,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * GsAppQuery:released-since: (nullable)
+ *
+ * A date/time which apps must have been released since (exclusive).
+ *
+ * Used to search for apps which have been updated recently.
+ *
+ * This may be %NULL to not filter on release date.
+ *
+ * Since: 43
+ */
+ props[PROP_RELEASED_SINCE] =
+ g_param_spec_boxed ("released-since", "Released Since",
+ "A date/time which apps must have been released since (exclusive).",
+ G_TYPE_DATE_TIME,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * GsAppQuery:is-curated:
+ *
+ * Whether apps must be curated (%GS_APP_QUERY_TRISTATE_TRUE), or not
+ * curated (%GS_APP_QUERY_TRISTATE_FALSE).
+ *
+ * If this is %GS_APP_QUERY_TRISTATE_UNSET, apps are not filtered by
+ * their curation state.
+ *
+ * ‘Curated’ apps have been reviewed and picked by an editor to be
+ * promoted to users in some way. They should be high quality and
+ * feature complete.
+ *
+ * Since: 43
+ */
+ props[PROP_IS_CURATED] =
+ g_param_spec_enum ("is-curated", "Is Curated",
+ "Whether apps must be curated, or not curated.",
+ GS_TYPE_APP_QUERY_TRISTATE,
+ GS_APP_QUERY_TRISTATE_UNSET,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * GsAppQuery:is-featured:
+ *
+ * Whether apps must be featured (%GS_APP_QUERY_TRISTATE_TRUE), or not
+ * featured (%GS_APP_QUERY_TRISTATE_FALSE).
+ *
+ * If this is %GS_APP_QUERY_TRISTATE_UNSET, apps are not filtered by
+ * their featured state.
+ *
+ * ‘Featured’ apps have been selected by the distribution or software
+ * source to be highlighted or promoted to users in some way. They
+ * should be high quality and feature complete.
+ *
+ * Since: 43
+ */
+ props[PROP_IS_FEATURED] =
+ g_param_spec_enum ("is-featured", "Is Featured",
+ "Whether apps must be featured, or not featured.",
+ GS_TYPE_APP_QUERY_TRISTATE,
+ GS_APP_QUERY_TRISTATE_UNSET,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * GsAppQuery:category: (nullable)
+ *
+ * A category which apps must be in.
+ *
+ * If this is %NULL, apps are not filtered by category.
+ *
+ * Since: 43
+ */
+ props[PROP_CATEGORY] =
+ g_param_spec_object ("category", "Category",
+ "A category which apps must be in.",
+ GS_TYPE_CATEGORY,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * GsAppQuery:is-installed:
+ *
+ * Whether apps must be installed (%GS_APP_QUERY_TRISTATE_TRUE), or not
+ * installed (%GS_APP_QUERY_TRISTATE_FALSE).
+ *
+ * If this is %GS_APP_QUERY_TRISTATE_UNSET, apps are not filtered by
+ * their installed state.
+ *
+ * Since: 43
+ */
+ props[PROP_IS_INSTALLED] =
+ g_param_spec_enum ("is-installed", "Is Installed",
+ "Whether apps must be installed, or not installed.",
+ GS_TYPE_APP_QUERY_TRISTATE,
+ GS_APP_QUERY_TRISTATE_UNSET,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * GsAppQuery:keywords:
+ *
+ * A set of search keywords which apps must match.
+ *
+ * Search matches may be done against multiple properties of the app,
+ * such as its name, description, supported content types, defined
+ * keywords, etc. The keywords in this property may be stemmed in an
+ * undefined way after being retrieved from #GsAppQuery.
+ *
+ * If this is %NULL, apps are not filtered by matches to this set of
+ * keywords. An empty array is considered equivalent to %NULL.
+ *
+ * Since: 43
+ */
+ props[PROP_KEYWORDS] =
+ g_param_spec_boxed ("keywords", "Keywords",
+ "A set of search keywords which apps must match.",
+ G_TYPE_STRV,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * GsAppQuery:alternate-of: (nullable)
+ *
+ * An app which apps must be related to.
+ *
+ * The definition of ‘related to’ depends on the code consuming
+ * #GsAppQuery, but it will typically be other applications which
+ * implement the same feature, or other applications which are packaged
+ * together with this one.
+ *
+ * If this is %NULL, apps are not filtered by alternatives.
+ *
+ * Since: 43
+ */
+ props[PROP_ALTERNATE_OF] =
+ g_param_spec_object ("alternate-of", "Alternate Of",
+ "An app which apps must be related to.",
+ GS_TYPE_APP,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * GsAppQuery:provides-tag: (nullable)
+ *
+ * A tag which apps must provide.
+ *
+ * The interpretation of the tag depends on #GsAppQuery:provides-type,
+ * which must not be %GS_APP_QUERY_PROVIDES_UNKNOWN if this is
+ * non-%NULL. Typically a tag will be a content type which the app
+ * implements, or the name of a printer which the app provides the
+ * driver for, etc.
+ *
+ * If this is %NULL, apps are not filtered by what they provide.
+ *
+ * Since: 43
+ */
+ props[PROP_PROVIDES_TAG] =
+ g_param_spec_string ("provides-tag", "Provides Tag",
+ "A tag which apps must provide.",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * GsAppQuery:provides-type:
+ *
+ * The type of #GsAppQuery:provides-tag.
+ *
+ * If this is %GS_APP_QUERY_PROVIDES_UNKNOWN, apps are not filtered by
+ * what they provide.
+ *
+ * Since: 43
+ */
+ props[PROP_PROVIDES_TYPE] =
+ g_param_spec_enum ("provides-type", "Provides Type",
+ "The type of #GsAppQuery:provides-tag.",
+ GS_TYPE_APP_QUERY_PROVIDES_TYPE, GS_APP_QUERY_PROVIDES_UNKNOWN,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ g_object_class_install_properties (object_class, G_N_ELEMENTS (props), props);
+}
+
+static void
+gs_app_query_init (GsAppQuery *self)
+{
+ self->is_curated = GS_APP_QUERY_TRISTATE_UNSET;
+ self->is_featured = GS_APP_QUERY_TRISTATE_UNSET;
+ self->is_installed = GS_APP_QUERY_TRISTATE_UNSET;
+ self->provides_type = GS_APP_QUERY_PROVIDES_UNKNOWN;
+}
+
+/**
+ * gs_app_query_new:
+ * @first_property_name: name of the first #GObject property
+ * @...: value for the first property, followed by additional property/value
+ * pairs, then a terminating %NULL
+ *
+ * Create a new #GsAppQuery containing the given query properties.
+ *
+ * Returns: (transfer full): a new #GsAppQuery
+ * Since: 43
+ */
+GsAppQuery *
+gs_app_query_new (const gchar *first_property_name,
+ ...)
+{
+ va_list args;
+ g_autoptr(GsAppQuery) query = NULL;
+
+ va_start (args, first_property_name);
+ query = GS_APP_QUERY (g_object_new_valist (GS_TYPE_APP_QUERY, first_property_name, args));
+ va_end (args);
+
+ return g_steal_pointer (&query);
+}
+
+/**
+ * gs_app_query_get_refine_flags:
+ * @self: a #GsAppQuery
+ *
+ * Get the value of #GsAppQuery:refine-flags.
+ *
+ * Returns: the refine flags for the query
+ * Since: 43
+ */
+GsPluginRefineFlags
+gs_app_query_get_refine_flags (GsAppQuery *self)
+{
+ g_return_val_if_fail (GS_IS_APP_QUERY (self), GS_PLUGIN_REFINE_FLAGS_NONE);
+
+ return self->refine_flags;
+}
+
+/**
+ * gs_app_query_get_max_results:
+ * @self: a #GsAppQuery
+ *
+ * Get the value of #GsAppQuery:max-results.
+ *
+ * Returns: the maximum number of results to return for the query, or `0` to
+ * indicate no limit
+ * Since: 43
+ */
+guint
+gs_app_query_get_max_results (GsAppQuery *self)
+{
+ g_return_val_if_fail (GS_IS_APP_QUERY (self), 0);
+
+ return self->max_results;
+}
+
+/**
+ * gs_app_query_get_dedupe_flags:
+ * @self: a #GsAppQuery
+ *
+ * Get the value of #GsAppQuery:dedupe-flags.
+ *
+ * Returns: the dedupe flags for the query
+ * Since: 43
+ */
+GsAppListFilterFlags
+gs_app_query_get_dedupe_flags (GsAppQuery *self)
+{
+ g_return_val_if_fail (GS_IS_APP_QUERY (self), GS_APP_LIST_FILTER_FLAG_NONE);
+
+ return self->dedupe_flags;
+}
+
+/**
+ * gs_app_query_get_sort_func:
+ * @self: a #GsAppQuery
+ * @user_data_out: (out) (transfer none) (optional) (nullable): return location
+ * for the #GsAppQuery:sort-user-data, or %NULL to ignore
+ *
+ * Get the value of #GsAppQuery:sort-func.
+ *
+ * Returns: (nullable): the sort function for the query
+ * Since: 43
+ */
+GsAppListSortFunc
+gs_app_query_get_sort_func (GsAppQuery *self,
+ gpointer *user_data_out)
+{
+ g_return_val_if_fail (GS_IS_APP_QUERY (self), NULL);
+
+ if (user_data_out != NULL)
+ *user_data_out = self->sort_user_data;
+
+ return self->sort_func;
+}
+
+/**
+ * gs_app_query_get_filter_func:
+ * @self: a #GsAppQuery
+ * @user_data_out: (out) (transfer none) (optional) (nullable): return location
+ * for the #GsAppQuery:filter-user-data, or %NULL to ignore
+ *
+ * Get the value of #GsAppQuery:filter-func.
+ *
+ * Returns: (nullable): the filter function for the query
+ * Since: 43
+ */
+GsAppListFilterFunc
+gs_app_query_get_filter_func (GsAppQuery *self,
+ gpointer *user_data_out)
+{
+ g_return_val_if_fail (GS_IS_APP_QUERY (self), NULL);
+
+ if (user_data_out != NULL)
+ *user_data_out = self->filter_user_data;
+
+ return self->filter_func;
+}
+
+/**
+ * gs_app_query_get_n_properties_set:
+ * @self: a #GsAppQuery
+ *
+ * Get the number of query properties which have been set.
+ *
+ * These are the properties which determine the query results, rather than ones
+ * which control refining the results (#GsAppQuery:refine-flags,
+ * #GsAppQuery:max-results, #GsAppQuery:dedupe-flags, #GsAppQuery:sort-func and
+ * its user data, #GsAppQuery:filter-func and its user data).
+ *
+ * Returns: number of properties set so they will affect query results
+ * Since: 43
+ */
+guint
+gs_app_query_get_n_properties_set (GsAppQuery *self)
+{
+ guint n = 0;
+
+ g_return_val_if_fail (GS_IS_APP_QUERY (self), 0);
+
+ if (self->provides_files != NULL)
+ n++;
+ if (self->released_since != NULL)
+ n++;
+ if (self->is_curated != GS_APP_QUERY_TRISTATE_UNSET)
+ n++;
+ if (self->is_featured != GS_APP_QUERY_TRISTATE_UNSET)
+ n++;
+ if (self->category != NULL)
+ n++;
+ if (self->is_installed != GS_APP_QUERY_TRISTATE_UNSET)
+ n++;
+ if (self->deployment_featured != NULL)
+ n++;
+ if (self->developers != NULL)
+ n++;
+ if (self->keywords != NULL)
+ n++;
+ if (self->alternate_of != NULL)
+ n++;
+ if (self->provides_tag != NULL)
+ n++;
+
+ return n;
+}
+
+/**
+ * gs_app_query_get_provides_files:
+ * @self: a #GsAppQuery
+ *
+ * Get the value of #GsAppQuery:provides-files.
+ *
+ * Returns: (nullable): a list of file paths which the apps must provide,
+ * or %NULL to not filter on file paths
+ * Since: 43
+ */
+const gchar * const *
+gs_app_query_get_provides_files (GsAppQuery *self)
+{
+ g_return_val_if_fail (GS_IS_APP_QUERY (self), NULL);
+
+ /* Always return %NULL or a non-empty array */
+ g_assert (self->provides_files == NULL || self->provides_files[0] != NULL);
+
+ return (const gchar * const *) self->provides_files;
+}
+
+/**
+ * gs_app_query_get_released_since:
+ * @self: a #GsAppQuery
+ *
+ * Get the value of #GsAppQuery:released-since.
+ *
+ * Returns: (nullable): a date/time which apps must have been released since,
+ * or %NULL to not filter on release date
+ * Since: 43
+ */
+GDateTime *
+gs_app_query_get_released_since (GsAppQuery *self)
+{
+ g_return_val_if_fail (GS_IS_APP_QUERY (self), NULL);
+
+ return self->released_since;
+}
+
+/**
+ * gs_app_query_get_is_curated:
+ * @self: a #GsAppQuery
+ *
+ * Get the value of #GsAppQuery:is-curated.
+ *
+ * Returns: %GS_APP_QUERY_TRISTATE_TRUE if apps must be curated,
+ * %GS_APP_QUERY_TRISTATE_FALSE if they must be not curated, or
+ * %GS_APP_QUERY_TRISTATE_UNSET if it doesn’t matter
+ * Since: 43
+ */
+GsAppQueryTristate
+gs_app_query_get_is_curated (GsAppQuery *self)
+{
+ g_return_val_if_fail (GS_IS_APP_QUERY (self), GS_APP_QUERY_TRISTATE_UNSET);
+
+ return self->is_curated;
+}
+
+/**
+ * gs_app_query_get_is_featured:
+ * @self: a #GsAppQuery
+ *
+ * Get the value of #GsAppQuery:is-featured.
+ *
+ * Returns: %GS_APP_QUERY_TRISTATE_TRUE if apps must be featured,
+ * %GS_APP_QUERY_TRISTATE_FALSE if they must be not featured, or
+ * %GS_APP_QUERY_TRISTATE_UNSET if it doesn’t matter
+ * Since: 43
+ */
+GsAppQueryTristate
+gs_app_query_get_is_featured (GsAppQuery *self)
+{
+ g_return_val_if_fail (GS_IS_APP_QUERY (self), GS_APP_QUERY_TRISTATE_UNSET);
+
+ return self->is_featured;
+}
+
+/**
+ * gs_app_query_get_category:
+ * @self: a #GsAppQuery
+ *
+ * Get the value of #GsAppQuery:category.
+ *
+ * Returns: (nullable) (transfer none): a category which apps must be part of,
+ * or %NULL to not filter on category
+ * Since: 43
+ */
+GsCategory *
+gs_app_query_get_category (GsAppQuery *self)
+{
+ g_return_val_if_fail (GS_IS_APP_QUERY (self), NULL);
+
+ return self->category;
+}
+
+/**
+ * gs_app_query_get_is_installed:
+ * @self: a #GsAppQuery
+ *
+ * Get the value of #GsAppQuery:is-installed.
+ *
+ * Returns: %GS_APP_QUERY_TRISTATE_TRUE if apps must be installed,
+ * %GS_APP_QUERY_TRISTATE_FALSE if they must be not installed, or
+ * %GS_APP_QUERY_TRISTATE_UNSET if it doesn’t matter
+ * Since: 43
+ */
+GsAppQueryTristate
+gs_app_query_get_is_installed (GsAppQuery *self)
+{
+ g_return_val_if_fail (GS_IS_APP_QUERY (self), GS_APP_QUERY_TRISTATE_UNSET);
+
+ return self->is_installed;
+}
+
+/**
+ * gs_app_query_get_deployment_featured:
+ * @self: a #GsAppQuery
+ *
+ * Get the value of #GsAppQuery:deployment-featured.
+ *
+ * Returns: (nullable): a list of `GnomeSoftware::DeploymentFeatured` app keys,
+ * which the apps have set in a custom key, or %NULL to not filter on this
+ * Since: 43
+ */
+const gchar * const *
+gs_app_query_get_deployment_featured (GsAppQuery *self)
+{
+ g_return_val_if_fail (GS_IS_APP_QUERY (self), NULL);
+
+ /* Always return %NULL or a non-empty array */
+ g_assert (self->deployment_featured == NULL || self->deployment_featured[0] != NULL);
+
+ return (const gchar * const *) self->deployment_featured;
+}
+
+/**
+ * gs_app_query_get_developers:
+ * @self: a #GsAppQuery
+ *
+ * Get the value of #GsAppQuery:developers.
+ *
+ * Returns: (nullable): a list of developers who provide the apps,
+ * or %NULL to not filter by it
+ * Since: 43
+ */
+const gchar * const *
+gs_app_query_get_developers (GsAppQuery *self)
+{
+ g_return_val_if_fail (GS_IS_APP_QUERY (self), NULL);
+
+ /* Always return %NULL or a non-empty array */
+ g_assert (self->developers == NULL || self->developers[0] != NULL);
+
+ return (const gchar * const *) self->developers;
+}
+
+/**
+ * gs_app_query_get_keywords:
+ * @self: a #GsAppQuery
+ *
+ * Get the value of #GsAppQuery:keywords.
+ *
+ * Returns: a set of search keywords which apps must match, or %NULL to not
+ * filter by it
+ * Since: 43
+ */
+const gchar * const *
+gs_app_query_get_keywords (GsAppQuery *self)
+{
+ g_return_val_if_fail (GS_IS_APP_QUERY (self), NULL);
+
+ /* Always return %NULL or a non-empty array */
+ g_assert (self->keywords == NULL || self->keywords[0] != NULL);
+
+ return (const gchar * const *) self->keywords;
+}
+
+/**
+ * gs_app_query_get_alternate_of:
+ * @self: a #GsAppQuery
+ *
+ * Get the value of #GsAppQuery:alternate-of.
+ *
+ * Returns: (nullable) (transfer none): an app which apps must be related to,
+ * or %NULL to not filter on alternates
+ * Since: 43
+ */
+GsApp *
+gs_app_query_get_alternate_of (GsAppQuery *self)
+{
+ g_return_val_if_fail (GS_IS_APP_QUERY (self), NULL);
+
+ return self->alternate_of;
+}
+
+/**
+ * gs_app_query_get_provides:
+ * @self: a #GsAppQuery
+ * @out_provides_tag: (transfer none) (optional) (nullable) (out): return
+ * location for the value of #GsAppQuery:provides-tag, or %NULL to ignore
+ *
+ * Get the value of #GsAppQuery:provides-type and #GsAppQuery:provides-tag.
+ *
+ * Returns: the type of tag to filter on, or %GS_APP_QUERY_PROVIDES_UNKNOWN to
+ * not filter on provides
+ * Since: 43
+ */
+GsAppQueryProvidesType
+gs_app_query_get_provides (GsAppQuery *self,
+ const gchar **out_provides_tag)
+{
+ g_return_val_if_fail (GS_IS_APP_QUERY (self), GS_APP_QUERY_PROVIDES_UNKNOWN);
+
+ if (out_provides_tag != NULL)
+ *out_provides_tag = self->provides_tag;
+
+ return self->provides_type;
+}