/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ /* * GData Client * Copyright (C) Philip Withnall 2009–2010 * Copyright (C) Red Hat, Inc. 2015 * * GData Client is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * GData Client 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with GData Client. If not, see . */ /** * SECTION:gdata-query * @short_description: GData query object * @stability: Stable * @include: gdata/gdata-query.h * * #GDataQuery represents a collection of query parameters used in a series of queries on a #GDataService. It allows the query parameters to be * set, with the aim of building a query URI using gdata_query_get_query_uri(). Pagination is supported using gdata_query_next_page() and * gdata_query_previous_page(). * * Each query can have an ETag associated with it, which is a unique identifier for the set of query results produced by the query. * Each time a query is made, gdata_service_query() will set the #GDataQuery:etag property of the accompanying query to a value returned by the * server. If the same query is made again (using the same #GDataQuery instance), the server can skip returning the resulting #GDataFeed if its * contents haven't changed (in this case, gdata_service_query() will return %NULL with an empty error). * * For this reason, code using #GDataQuery should be careful when reusing #GDataQuery instances: the code should either unset #GDataQuery:etag after * every query or (preferably) gracefully handle the case where gdata_service_query() returns %NULL to signify unchanged results. * * Every time a property of a #GDataQuery instance is changed, the instance's ETag will be unset. * * For more information on the standard GData query parameters supported by #GDataQuery, see the online documentation. */ #include #include #include "gdata-query.h" #include "gdata-private.h" #include "gdata-types.h" static void gdata_query_finalize (GObject *object); static void gdata_query_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec); static void gdata_query_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec); static void get_query_uri (GDataQuery *self, const gchar *feed_uri, GString *query_uri, gboolean *params_started); struct _GDataQueryPrivate { /* Standard query parameters (see: http://code.google.com/apis/gdata/docs/2.0/reference.html#Queries) */ gchar *q; gchar *q_internal; gchar *categories; gchar *author; gint64 updated_min; gint64 updated_max; gint64 published_min; gint64 published_max; guint start_index; gboolean is_strict; guint max_results; /* Pagination management. The type of pagination is set as * pagination_type, and should be set in the init() vfunc implementation * of any class derived from GDataQuery. It defaults to * %GDATA_QUERY_PAGINATION_INDEXED, which most subclasses will not want. * * The next_uri, previous_uri or next_page_token are set by * #GDataService if a query returns a new #GDataFeed containing them. If * the user then calls next_page() or previous_page(), use_next_page or * use_previous_page are set as appropriate, and the next call to * get_uri() will return a URI for the next or previous page. This might * be next_uri, previous_uri, or a constructed URI which appends the * next_page_token. * * Note that %GDATA_QUERY_PAGINATION_TOKENS does not support returning * to the previous page. * * It is not invalid to have use_next_page set and to not have a * next_uri for %GDATA_QUERY_PAGINATION_URIS; or to not have a * next_page_token for %GDATA_QUERY_PAGINATION_TOKENS: this signifies * that the current set of results are the last page. There are no * further pages. Similarly for use_previous_page and a %NULL * previous_page. */ GDataQueryPaginationType pagination_type; gchar *next_uri; gchar *previous_uri; gchar *next_page_token; gboolean use_next_page; gboolean use_previous_page; gchar *etag; }; enum { PROP_Q = 1, PROP_CATEGORIES, PROP_AUTHOR, PROP_UPDATED_MIN, PROP_UPDATED_MAX, PROP_PUBLISHED_MIN, PROP_PUBLISHED_MAX, PROP_START_INDEX, PROP_IS_STRICT, PROP_MAX_RESULTS, PROP_ETAG }; G_DEFINE_TYPE (GDataQuery, gdata_query, G_TYPE_OBJECT) static void gdata_query_class_init (GDataQueryClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); g_type_class_add_private (klass, sizeof (GDataQueryPrivate)); gobject_class->set_property = gdata_query_set_property; gobject_class->get_property = gdata_query_get_property; gobject_class->finalize = gdata_query_finalize; klass->get_query_uri = get_query_uri; /** * GDataQuery:q: * * A full-text query string. * * When creating a query, list search terms separated by spaces, in the form term1 term2 term3. * (As with all of the query parameter values, the spaces must be URL encoded.) The service returns all entries that match all of the * search terms (like using AND between terms). Like Google's web search, a service searches on complete words (and related words with * the same stem), not substrings. * * To search for an exact phrase, enclose the phrase in quotation marks: "exact phrase". * * To exclude entries that match a given term, use the form -term. * * The search is case-insensitive. * * Example: to search for all entries that contain the exact phrase "Elizabeth Bennet" and the word "Darcy" but don't contain the * word "Austen", use the following query: "Elizabeth Bennet" Darcy -Austen. */ g_object_class_install_property (gobject_class, PROP_Q, g_param_spec_string ("q", "Query terms", "Query terms for which to search.", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * GDataQuery:categories: * * A category filter. * * You can query on multiple categories by listing multiple categories separated by slashes. The service returns all entries that match all * of the categories (like using AND between terms). For example: Fritz/Laurie returns * entries that match both categories ("Fritz" and "Laurie"). * * To do an OR between terms, use a pipe character (|). For example: Fritz\%7CLaurie returns * entries that match either category. * * An entry matches a specified category if the entry is in a category that has a matching term or label, as defined in the Atom * specification. (Roughly, the "term" is the internal string used by the software to identify the category, while the "label" is the * human-readable string presented to a user in a user interface.) * * To exclude entries that match a given category, use the form -categoryname. * * To query for a category that has a scheme – such as <category scheme="urn:google.com" term="public"/> – you must * place the scheme in curly braces before the category name. For example: {urn:google.com}public. To match a category * that has no scheme, use an empty pair of curly braces. If you don't specify curly braces, then categories in any scheme will match. * * The above features can be combined. For example: A|-{urn:google.com}B/-C means (A OR (NOT B)) AND (NOT C). */ g_object_class_install_property (gobject_class, PROP_CATEGORIES, g_param_spec_string ("categories", "Category string", "Category search string.", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * GDataQuery:author: * * An entry author. The service returns entries where the author name and/or e-mail address match your query string. */ g_object_class_install_property (gobject_class, PROP_AUTHOR, g_param_spec_string ("author", "Author", "Author search string.", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * GDataQuery:updated-min: * * Lower bound on the entry update date, inclusive. */ g_object_class_install_property (gobject_class, PROP_UPDATED_MIN, g_param_spec_int64 ("updated-min", "Minimum update date", "Minimum date for updates on returned entries.", -1, G_MAXINT64, -1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * GDataQuery:updated-max: * * Upper bound on the entry update date, exclusive. */ g_object_class_install_property (gobject_class, PROP_UPDATED_MAX, g_param_spec_int64 ("updated-max", "Maximum update date", "Maximum date for updates on returned entries.", -1, G_MAXINT64, -1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * GDataQuery:published-min: * * Lower bound on the entry publish date, inclusive. */ g_object_class_install_property (gobject_class, PROP_PUBLISHED_MIN, g_param_spec_int64 ("published-min", "Minimum publish date", "Minimum date for returned entries to be published.", -1, G_MAXINT64, -1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * GDataQuery:published-max: * * Upper bound on the entry publish date, exclusive. */ g_object_class_install_property (gobject_class, PROP_PUBLISHED_MAX, g_param_spec_int64 ("published-max", "Maximum publish date", "Maximum date for returned entries to be published.", -1, G_MAXINT64, -1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * GDataQuery:start-index: * * The one-based index of the first result to be retrieved. Use gdata_query_next_page() and gdata_query_previous_page() to * implement pagination, rather than manually changing #GDataQuery:start-index. * * Use 0 to not specify a start index. */ g_object_class_install_property (gobject_class, PROP_START_INDEX, g_param_spec_uint ("start-index", "Start index", "One-based result start index.", 0, G_MAXUINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * GDataQuery:is-strict: * * Strict query parameter checking. If this is enabled, an error will be returned by the online service if a parameter is * not recognised. * * Since: 0.2.0 */ g_object_class_install_property (gobject_class, PROP_IS_STRICT, g_param_spec_boolean ("is-strict", "Strict?", "Should the server be strict about the query?", FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * GDataQuery:max-results: * * Maximum number of results to be retrieved. Most services have a default #GDataQuery:max-results size imposed by the server; if you wish * to receive the entire feed, specify a large number such as %G_MAXUINT for this property. * * Use 0 to not specify a maximum number of results. */ g_object_class_install_property (gobject_class, PROP_MAX_RESULTS, g_param_spec_uint ("max-results", "Maximum number of results", "The maximum number of entries to return.", 0, G_MAXUINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * GDataQuery:etag: * * The ETag against which to check for updates. If the server-side ETag matches this one, the requested feed hasn't changed, and is not * returned unnecessarily. * * Setting any of the other query properties will unset the ETag, as ETags match against entire queries. If the ETag should be used in a * query, it must be set again using gdata_query_set_etag() after setting any other properties. * * Since: 0.2.0 */ g_object_class_install_property (gobject_class, PROP_ETAG, g_param_spec_string ("etag", "ETag", "An ETag against which to check.", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); } static void gdata_query_init (GDataQuery *self) { self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GDATA_TYPE_QUERY, GDataQueryPrivate); self->priv->updated_min = -1; self->priv->updated_max = -1; self->priv->published_min = -1; self->priv->published_max = -1; _gdata_query_set_pagination_type (self, GDATA_QUERY_PAGINATION_INDEXED); } static void gdata_query_finalize (GObject *object) { GDataQueryPrivate *priv = GDATA_QUERY (object)->priv; g_free (priv->q); g_free (priv->q_internal); g_free (priv->categories); g_free (priv->author); g_free (priv->next_uri); g_free (priv->previous_uri); g_free (priv->etag); g_free (priv->next_page_token); /* Chain up to the parent class */ G_OBJECT_CLASS (gdata_query_parent_class)->finalize (object); } static void gdata_query_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { GDataQueryPrivate *priv = GDATA_QUERY (object)->priv; switch (property_id) { case PROP_Q: g_value_set_string (value, priv->q); break; case PROP_CATEGORIES: g_value_set_string (value, priv->categories); break; case PROP_AUTHOR: g_value_set_string (value, priv->author); break; case PROP_UPDATED_MIN: g_value_set_int64 (value, priv->updated_min); break; case PROP_UPDATED_MAX: g_value_set_int64 (value, priv->updated_max); break; case PROP_PUBLISHED_MIN: g_value_set_int64 (value, priv->published_min); break; case PROP_PUBLISHED_MAX: g_value_set_int64 (value, priv->published_max); break; case PROP_START_INDEX: g_value_set_uint (value, priv->start_index); break; case PROP_IS_STRICT: g_value_set_boolean (value, priv->is_strict); break; case PROP_MAX_RESULTS: g_value_set_uint (value, priv->max_results); break; case PROP_ETAG: g_value_set_string (value, priv->etag); break; default: /* We don't have any other property... */ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void gdata_query_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { GDataQuery *self = GDATA_QUERY (object); switch (property_id) { case PROP_Q: gdata_query_set_q (self, g_value_get_string (value)); break; case PROP_CATEGORIES: gdata_query_set_categories (self, g_value_get_string (value)); break; case PROP_AUTHOR: gdata_query_set_author (self, g_value_get_string (value)); break; case PROP_UPDATED_MIN: gdata_query_set_updated_min (self, g_value_get_int64 (value)); break; case PROP_UPDATED_MAX: gdata_query_set_updated_max (self, g_value_get_int64 (value)); break; case PROP_PUBLISHED_MIN: gdata_query_set_published_min (self, g_value_get_int64 (value)); break; case PROP_PUBLISHED_MAX: gdata_query_set_published_max (self, g_value_get_int64 (value)); break; case PROP_START_INDEX: gdata_query_set_start_index (self, g_value_get_uint (value)); break; case PROP_IS_STRICT: gdata_query_set_is_strict (self, g_value_get_boolean (value)); break; case PROP_MAX_RESULTS: gdata_query_set_max_results (self, g_value_get_uint (value)); break; case PROP_ETAG: gdata_query_set_etag (self, g_value_get_string (value)); break; default: /* We don't have any other property... */ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void get_query_uri (GDataQuery *self, const gchar *feed_uri, GString *query_uri, gboolean *params_started) { GDataQueryPrivate *priv = self->priv; #define APPEND_SEP g_string_append_c (query_uri, (*params_started == FALSE) ? '?' : '&'); *params_started = TRUE; /* Categories */ if (priv->categories != NULL) { g_string_append (query_uri, "/-/"); g_string_append_uri_escaped (query_uri, priv->categories, "/", FALSE); } /* q param */ if (priv->q != NULL || priv->q_internal != NULL) { APPEND_SEP g_string_append (query_uri, "q="); if (priv->q != NULL) { g_string_append_uri_escaped (query_uri, priv->q, NULL, FALSE); if (priv->q_internal != NULL) g_string_append (query_uri, "%20and%20"); } if (priv->q_internal != NULL) g_string_append_uri_escaped (query_uri, priv->q_internal, NULL, FALSE); } if (priv->author != NULL) { APPEND_SEP g_string_append (query_uri, "author="); g_string_append_uri_escaped (query_uri, priv->author, NULL, FALSE); } if (priv->updated_min != -1) { gchar *updated_min; APPEND_SEP g_string_append (query_uri, "updated-min="); updated_min = gdata_parser_int64_to_iso8601 (priv->updated_min); g_string_append (query_uri, updated_min); g_free (updated_min); } if (priv->updated_max != -1) { gchar *updated_max; APPEND_SEP g_string_append (query_uri, "updated-max="); updated_max = gdata_parser_int64_to_iso8601 (priv->updated_max); g_string_append (query_uri, updated_max); g_free (updated_max); } if (priv->published_min != -1) { gchar *published_min; APPEND_SEP g_string_append (query_uri, "published-min="); published_min = gdata_parser_int64_to_iso8601 (priv->published_min); g_string_append (query_uri, published_min); g_free (published_min); } if (priv->published_max != -1) { gchar *published_max; APPEND_SEP g_string_append (query_uri, "published-max="); published_max = gdata_parser_int64_to_iso8601 (priv->published_max); g_string_append (query_uri, published_max); g_free (published_max); } if (priv->start_index > 0) { APPEND_SEP g_string_append_printf (query_uri, "start-index=%u", priv->start_index); } if (priv->is_strict == TRUE) { APPEND_SEP g_string_append (query_uri, "strict=true"); } if (priv->max_results > 0) { APPEND_SEP g_string_append_printf (query_uri, "max-results=%u", priv->max_results); } if (priv->pagination_type == GDATA_QUERY_PAGINATION_TOKENS && priv->use_next_page && priv->next_page_token != NULL && *priv->next_page_token != '\0') { APPEND_SEP g_string_append (query_uri, "pageToken="); g_string_append_uri_escaped (query_uri, priv->next_page_token, NULL, FALSE); } } /** * gdata_query_new: * @q: (allow-none): a query string, or %NULL * * Creates a new #GDataQuery with its #GDataQuery:q property set to @q. * * Return value: a new #GDataQuery */ GDataQuery * gdata_query_new (const gchar *q) { return g_object_new (GDATA_TYPE_QUERY, "q", q, NULL); } /** * gdata_query_new_with_limits: * @q: (allow-none): a query string, or %NULL * @start_index: a one-based start index for the results, or 0 * @max_results: the maximum number of results to return, or 0 * * Creates a new #GDataQuery with its #GDataQuery:q property set to @q, and the limits @start_index and @max_results * applied. * * Return value: a new #GDataQuery */ GDataQuery * gdata_query_new_with_limits (const gchar *q, guint start_index, guint max_results) { return g_object_new (GDATA_TYPE_QUERY, "q", q, "start-index", start_index, "max-results", max_results, NULL); } /** * gdata_query_get_query_uri: * @self: a #GDataQuery * @feed_uri: the feed URI on which to build the query URI * * Builds a query URI from the given base feed URI, using the properties of the #GDataQuery. This function will take care * of all necessary URI escaping, so it should not be done beforehand. * * The query URI is what functions like gdata_service_query() use to query the online service. * * Return value: a query URI; free with g_free() */ gchar * gdata_query_get_query_uri (GDataQuery *self, const gchar *feed_uri) { GDataQueryClass *klass; GString *query_uri; gboolean params_started; g_return_val_if_fail (GDATA_IS_QUERY (self), NULL); g_return_val_if_fail (feed_uri != NULL, NULL); /* Check to see if we're paginating first */ if (self->priv->pagination_type == GDATA_QUERY_PAGINATION_URIS) { if (self->priv->use_next_page) return g_strdup (self->priv->next_uri); if (self->priv->use_previous_page) return g_strdup (self->priv->previous_uri); } klass = GDATA_QUERY_GET_CLASS (self); g_assert (klass->get_query_uri != NULL); /* Determine whether the first param has already been appended (e.g. it exists in the feed_uri) */ params_started = (strstr (feed_uri, "?") != NULL) ? TRUE : FALSE; /* Build the query URI */ query_uri = g_string_new (feed_uri); klass->get_query_uri (self, feed_uri, query_uri, ¶ms_started); return g_string_free (query_uri, FALSE); } /* Used internally by child classes of GDataQuery to add search clauses that represent service-specific * query properties. For example, in the Drive v2 API, certain GDataDocumentsQuery properties like * show-deleted and show-folders no longer have their own parameters, but have to be specified as a search * clause in the query string. */ void _gdata_query_add_q_internal (GDataQuery *self, const gchar *q) { GDataQueryPrivate *priv = self->priv; GString *str; g_return_if_fail (GDATA_IS_QUERY (self)); g_return_if_fail (q != NULL && q[0] != '\0'); str = g_string_new (priv->q_internal); /* Search parameters: https://developers.google.com/drive/web/search-parameters */ if (str->len > 0) g_string_append (str, " and "); g_string_append (str, q); g_free (priv->q_internal); priv->q_internal = g_string_free (str, FALSE); } /* Used internally by child classes of GDataQuery to clear the internal query string when building the * query URI in GDataQueryClass->get_query_uri */ void _gdata_query_clear_q_internal (GDataQuery *self) { GDataQueryPrivate *priv = self->priv; g_return_if_fail (GDATA_IS_QUERY (self)); g_free (priv->q_internal); priv->q_internal = NULL; } /** * gdata_query_get_q: * @self: a #GDataQuery * * Gets the #GDataQuery:q property. * * Return value: the q property, or %NULL if it is unset */ const gchar * gdata_query_get_q (GDataQuery *self) { g_return_val_if_fail (GDATA_IS_QUERY (self), NULL); return self->priv->q; } /** * gdata_query_set_q: * @self: a #GDataQuery * @q: (allow-none): a new query string, or %NULL * * Sets the #GDataQuery:q property of the #GDataQuery to the new query string, @q. * * Set @q to %NULL to unset the property in the query URI. */ void gdata_query_set_q (GDataQuery *self, const gchar *q) { g_return_if_fail (GDATA_IS_QUERY (self)); g_free (self->priv->q); self->priv->q = g_strdup (q); g_object_notify (G_OBJECT (self), "q"); /* Our current ETag will no longer be relevant */ gdata_query_set_etag (self, NULL); } /** * gdata_query_get_categories: * @self: a #GDataQuery * * Gets the #GDataQuery:categories property. * * Return value: the categories property, or %NULL if it is unset */ const gchar * gdata_query_get_categories (GDataQuery *self) { g_return_val_if_fail (GDATA_IS_QUERY (self), NULL); return self->priv->categories; } /** * gdata_query_set_categories: * @self: a #GDataQuery * @categories: (allow-none): the new category string, or %NULL * * Sets the #GDataQuery:categories property of the #GDataQuery to the new category string, @categories. * * Set @categories to %NULL to unset the property in the query URI. */ void gdata_query_set_categories (GDataQuery *self, const gchar *categories) { g_return_if_fail (GDATA_IS_QUERY (self)); g_free (self->priv->categories); self->priv->categories = g_strdup (categories); g_object_notify (G_OBJECT (self), "categories"); /* Our current ETag will no longer be relevant */ gdata_query_set_etag (self, NULL); } /** * gdata_query_get_author: * @self: a #GDataQuery * * Gets the #GDataQuery:author property. * * Return value: the author property, or %NULL if it is unset */ const gchar * gdata_query_get_author (GDataQuery *self) { g_return_val_if_fail (GDATA_IS_QUERY (self), NULL); return self->priv->author; } /** * gdata_query_set_author: * @self: a #GDataQuery * @author: (allow-none): the new author string, or %NULL * * Sets the #GDataQuery:author property of the #GDataQuery to the new author string, @author. * * Set @author to %NULL to unset the property in the query URI. */ void gdata_query_set_author (GDataQuery *self, const gchar *author) { g_return_if_fail (GDATA_IS_QUERY (self)); g_free (self->priv->author); self->priv->author = g_strdup (author); g_object_notify (G_OBJECT (self), "author"); /* Our current ETag will no longer be relevant */ gdata_query_set_etag (self, NULL); } /** * gdata_query_get_updated_min: * @self: a #GDataQuery * * Gets the #GDataQuery:updated-min property. If the property is unset, -1 will be returned. * * Return value: the updated-min property, or -1 */ gint64 gdata_query_get_updated_min (GDataQuery *self) { g_return_val_if_fail (GDATA_IS_QUERY (self), -1); return self->priv->updated_min; } /** * gdata_query_set_updated_min: * @self: a #GDataQuery * @updated_min: the new minimum update time, or -1 * * Sets the #GDataQuery:updated-min property of the #GDataQuery to the new minimum update time, @updated_min. * * Set @updated_min to -1 to unset the property in the query URI. */ void gdata_query_set_updated_min (GDataQuery *self, gint64 updated_min) { g_return_if_fail (GDATA_IS_QUERY (self)); g_return_if_fail (updated_min >= -1); self->priv->updated_min = updated_min; g_object_notify (G_OBJECT (self), "updated-min"); /* Our current ETag will no longer be relevant */ gdata_query_set_etag (self, NULL); } /** * gdata_query_get_updated_max: * @self: a #GDataQuery * * Gets the #GDataQuery:updated-max property. If the property is unset, -1 will be returned. * * Return value: the updated-max property, or -1 */ gint64 gdata_query_get_updated_max (GDataQuery *self) { g_return_val_if_fail (GDATA_IS_QUERY (self), -1); return self->priv->updated_max; } /** * gdata_query_set_updated_max: * @self: a #GDataQuery * @updated_max: the new maximum update time, or -1 * * Sets the #GDataQuery:updated-max property of the #GDataQuery to the new maximum update time, @updated_max. * * Set @updated_max to -1 to unset the property in the query URI. */ void gdata_query_set_updated_max (GDataQuery *self, gint64 updated_max) { g_return_if_fail (GDATA_IS_QUERY (self)); g_return_if_fail (updated_max >= -1); self->priv->updated_max = updated_max; g_object_notify (G_OBJECT (self), "updated-max"); /* Our current ETag will no longer be relevant */ gdata_query_set_etag (self, NULL); } /** * gdata_query_get_published_min: * @self: a #GDataQuery * * Gets the #GDataQuery:published-min property. If the property is unset, -1 will be returned. * * Return value: the published-min property, or -1 */ gint64 gdata_query_get_published_min (GDataQuery *self) { g_return_val_if_fail (GDATA_IS_QUERY (self), -1); return self->priv->published_min; } /** * gdata_query_set_published_min: * @self: a #GDataQuery * @published_min: the new minimum publish time, or -1 * * Sets the #GDataQuery:published-min property of the #GDataQuery to the new minimum publish time, @published_min. * * Set @published_min to -1 to unset the property in the query URI. */ void gdata_query_set_published_min (GDataQuery *self, gint64 published_min) { g_return_if_fail (GDATA_IS_QUERY (self)); g_return_if_fail (published_min >= -1); self->priv->published_min = published_min; g_object_notify (G_OBJECT (self), "published-min"); /* Our current ETag will no longer be relevant */ gdata_query_set_etag (self, NULL); } /** * gdata_query_get_published_max: * @self: a #GDataQuery * * Gets the #GDataQuery:published-max property. If the property is unset, -1 will be returned. * * Return value: the published-max property, or -1 */ gint64 gdata_query_get_published_max (GDataQuery *self) { g_return_val_if_fail (GDATA_IS_QUERY (self), -1); return self->priv->published_max; } /** * gdata_query_set_published_max: * @self: a #GDataQuery * @published_max: the new maximum publish time, or -1 * * Sets the #GDataQuery:published-max property of the #GDataQuery to the new maximum publish time, @published_max. * * Set @published_max to -1 to unset the property in the query URI. */ void gdata_query_set_published_max (GDataQuery *self, gint64 published_max) { g_return_if_fail (GDATA_IS_QUERY (self)); g_return_if_fail (published_max >= -1); self->priv->published_max = published_max; g_object_notify (G_OBJECT (self), "published-max"); /* Our current ETag will no longer be relevant */ gdata_query_set_etag (self, NULL); } /** * gdata_query_get_start_index: * @self: a #GDataQuery * * Gets the #GDataQuery:start-index property. * * Return value: the start index property, or 0 if it is unset */ guint gdata_query_get_start_index (GDataQuery *self) { g_return_val_if_fail (GDATA_IS_QUERY (self), 0); return self->priv->start_index; } /** * gdata_query_set_start_index: * @self: a #GDataQuery * @start_index: the new start index, or 0 * * Sets the #GDataQuery:start-index property of the #GDataQuery to the new one-based start index, @start_index. * * Set @start_index to 0 to unset the property in the query URI. */ void gdata_query_set_start_index (GDataQuery *self, guint start_index) { g_return_if_fail (GDATA_IS_QUERY (self)); self->priv->start_index = start_index; g_object_notify (G_OBJECT (self), "start-index"); /* Our current ETag will no longer be relevant */ gdata_query_set_etag (self, NULL); } /** * gdata_query_is_strict: * @self: a #GDataQuery * * Gets the #GDataQuery:is-strict property. * * Return value: the strict property * * Since: 0.2.0 */ gboolean gdata_query_is_strict (GDataQuery *self) { g_return_val_if_fail (GDATA_IS_QUERY (self), FALSE); return self->priv->is_strict; } /** * gdata_query_set_is_strict: * @self: a #GDataQuery * @is_strict: the new strict value * * Sets the #GDataQuery:is-strict property of the #GDataQuery to the new strict value, @is_strict. * * Since: 0.2.0 */ void gdata_query_set_is_strict (GDataQuery *self, gboolean is_strict) { g_return_if_fail (GDATA_IS_QUERY (self)); self->priv->is_strict = is_strict; g_object_notify (G_OBJECT (self), "is-strict"); /* Our current ETag will no longer be relevant */ gdata_query_set_etag (self, NULL); } /** * gdata_query_get_max_results: * @self: a #GDataQuery * * Gets the #GDataQuery:max-results property. * * Return value: the maximum results property, or 0 if it is unset */ guint gdata_query_get_max_results (GDataQuery *self) { g_return_val_if_fail (GDATA_IS_QUERY (self), 0); return self->priv->max_results; } /** * gdata_query_set_max_results: * @self: a #GDataQuery * @max_results: the new maximum results value, or 0 * * Sets the #GDataQuery:max-results property of the #GDataQuery to the new maximum results value, @max_results. * * Set @max_results to 0 to unset the property in the query URI. */ void gdata_query_set_max_results (GDataQuery *self, guint max_results) { g_return_if_fail (GDATA_IS_QUERY (self)); self->priv->max_results = max_results; g_object_notify (G_OBJECT (self), "max-results"); /* Our current ETag will no longer be relevant */ gdata_query_set_etag (self, NULL); } /** * gdata_query_get_etag: * @self: a #GDataQuery * * Gets the #GDataQuery:etag property. * * Return value: the ETag property, or %NULL if it is unset * * Since: 0.2.0 */ const gchar * gdata_query_get_etag (GDataQuery *self) { g_return_val_if_fail (GDATA_IS_QUERY (self), NULL); return self->priv->etag; } /** * gdata_query_set_etag: * @self: a #GDataQuery * @etag: (allow-none): the new ETag, or %NULL * * Sets the #GDataQuery:etag property of the #GDataQuery to the new ETag, @etag. * * Set @etag to %NULL to not check against the server-side ETag. * * Since: 0.2.0 */ void gdata_query_set_etag (GDataQuery *self, const gchar *etag) { g_return_if_fail (GDATA_IS_QUERY (self)); g_free (self->priv->etag); self->priv->etag = g_strdup (etag); g_object_notify (G_OBJECT (self), "etag"); } void _gdata_query_clear_pagination (GDataQuery *self) { g_return_if_fail (GDATA_IS_QUERY (self)); switch (self->priv->pagination_type) { case GDATA_QUERY_PAGINATION_INDEXED: /* Nothing to do here: indexes can always be incremented. */ break; case GDATA_QUERY_PAGINATION_URIS: g_clear_pointer (&self->priv->next_uri, g_free); g_clear_pointer (&self->priv->previous_uri, g_free); break; case GDATA_QUERY_PAGINATION_TOKENS: g_clear_pointer (&self->priv->next_page_token, g_free); break; default: g_assert_not_reached (); } self->priv->use_next_page = FALSE; self->priv->use_previous_page = FALSE; } void _gdata_query_set_pagination_type (GDataQuery *self, GDataQueryPaginationType type) { g_debug ("%s: Pagination type set to %u", G_STRFUNC, type); _gdata_query_clear_pagination (self); self->priv->pagination_type = type; } void _gdata_query_set_next_page_token (GDataQuery *self, const gchar *next_page_token) { g_return_if_fail (GDATA_IS_QUERY (self)); g_return_if_fail (self->priv->pagination_type == GDATA_QUERY_PAGINATION_TOKENS); g_free (self->priv->next_page_token); self->priv->next_page_token = g_strdup (next_page_token); } void _gdata_query_set_next_uri (GDataQuery *self, const gchar *next_uri) { g_return_if_fail (GDATA_IS_QUERY (self)); g_return_if_fail (self->priv->pagination_type == GDATA_QUERY_PAGINATION_URIS); g_free (self->priv->next_uri); self->priv->next_uri = g_strdup (next_uri); } gboolean _gdata_query_is_finished (GDataQuery *self) { g_return_val_if_fail (GDATA_IS_QUERY (self), FALSE); switch (self->priv->pagination_type) { case GDATA_QUERY_PAGINATION_INDEXED: return FALSE; case GDATA_QUERY_PAGINATION_URIS: return (self->priv->next_uri == NULL && self->priv->use_next_page); case GDATA_QUERY_PAGINATION_TOKENS: return (self->priv->next_page_token == NULL && self->priv->use_next_page); default: g_assert_not_reached (); } } void _gdata_query_set_previous_uri (GDataQuery *self, const gchar *previous_uri) { g_return_if_fail (GDATA_IS_QUERY (self)); g_return_if_fail (self->priv->pagination_type == GDATA_QUERY_PAGINATION_URIS); g_free (self->priv->previous_uri); self->priv->previous_uri = g_strdup (previous_uri); } /** * gdata_query_next_page: * @self: a #GDataQuery * * Changes the state of the #GDataQuery such that when gdata_query_get_query_uri() is next called, it will build the * query URI for the next page in the result set. * * Ideally, the URI of the next page is retrieved from a feed automatically when gdata_service_query() is called, but * gdata_query_next_page() will fall back to using #GDataQuery:start-index to emulate true pagination if this fails. * * You should not implement pagination manually using #GDataQuery:start-index. */ void gdata_query_next_page (GDataQuery *self) { GDataQueryPrivate *priv = self->priv; g_return_if_fail (GDATA_IS_QUERY (self)); switch (self->priv->pagination_type) { case GDATA_QUERY_PAGINATION_INDEXED: if (priv->start_index == 0) priv->start_index++; priv->start_index += priv->max_results; break; case GDATA_QUERY_PAGINATION_URIS: case GDATA_QUERY_PAGINATION_TOKENS: priv->use_next_page = TRUE; priv->use_previous_page = FALSE; break; default: g_assert_not_reached (); } /* Our current ETag will no longer be relevant */ gdata_query_set_etag (self, NULL); } /** * gdata_query_previous_page: * @self: a #GDataQuery * * Changes the state of the #GDataQuery such that when gdata_query_get_query_uri() is next called, it will build the * query URI for the previous page in the result set. * * See the documentation for gdata_query_next_page() for an explanation of how query URIs from the feeds are used to this end. * * Return value: %TRUE if there is a previous page and it has been switched to, %FALSE otherwise */ gboolean gdata_query_previous_page (GDataQuery *self) { GDataQueryPrivate *priv = self->priv; gboolean retval; g_return_val_if_fail (GDATA_IS_QUERY (self), FALSE); switch (self->priv->pagination_type) { case GDATA_QUERY_PAGINATION_INDEXED: if (priv->start_index <= priv->max_results) { retval = FALSE; } else { priv->start_index -= priv->max_results; if (priv->start_index == 1) priv->start_index--; retval = TRUE; } break; case GDATA_QUERY_PAGINATION_URIS: if (priv->previous_uri != NULL) { priv->use_next_page = FALSE; priv->use_previous_page = TRUE; retval = TRUE; } else { retval = FALSE; } break; case GDATA_QUERY_PAGINATION_TOKENS: /* There are no previous page tokens, unfortunately. */ retval = FALSE; break; default: g_assert_not_reached (); } if (retval) { /* Our current ETag will no longer be relevant */ gdata_query_set_etag (self, NULL); } return retval; }