1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 /*
3  * GData Client
4  * Copyright (C) Philip Withnall 2009–2010 <philip@tecnocode.co.uk>
5  * Copyright (C) Red Hat, Inc. 2015
6  *
7  * GData Client is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * GData Client is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with GData Client.  If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 /**
22  * SECTION:gdata-query
23  * @short_description: GData query object
24  * @stability: Stable
25  * @include: gdata/gdata-query.h
26  *
27  * #GDataQuery represents a collection of query parameters used in a series of queries on a #GDataService. It allows the query parameters to be
28  * set, with the aim of building a query URI using gdata_query_get_query_uri(). Pagination is supported using gdata_query_next_page() and
29  * gdata_query_previous_page().
30  *
31  * Each query can have an ETag associated with it, which is a unique identifier for the set of query results produced by the query.
32  * 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
33  * server. If the same query is made again (using the same #GDataQuery instance), the server can skip returning the resulting #GDataFeed if its
34  * contents haven't changed (in this case, gdata_service_query() will return %NULL with an empty error).
35  *
36  * For this reason, code using #GDataQuery should be careful when reusing #GDataQuery instances: the code should either unset #GDataQuery:etag after
37  * every query or (preferably) gracefully handle the case where gdata_service_query() returns %NULL to signify unchanged results.
38  *
39  * Every time a property of a #GDataQuery instance is changed, the instance's ETag will be unset.
40  *
41  * For more information on the standard GData query parameters supported by #GDataQuery, see the <ulink type="http"
42  * url="http://code.google.com/apis/gdata/docs/2.0/reference.html#Queries">online documentation</ulink>.
43  */
44 
45 #include <glib.h>
46 #include <string.h>
47 
48 #include "gdata-query.h"
49 #include "gdata-private.h"
50 #include "gdata-types.h"
51 
52 static void gdata_query_finalize (GObject *object);
53 static void gdata_query_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
54 static void gdata_query_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec);
55 static void get_query_uri (GDataQuery *self, const gchar *feed_uri, GString *query_uri, gboolean *params_started);
56 
57 struct _GDataQueryPrivate {
58 	/* Standard query parameters (see: http://code.google.com/apis/gdata/docs/2.0/reference.html#Queries) */
59 	gchar *q;
60 	gchar *q_internal;
61 	gchar *categories;
62 	gchar *author;
63 	gint64 updated_min;
64 	gint64 updated_max;
65 	gint64 published_min;
66 	gint64 published_max;
67 	guint start_index;
68 	gboolean is_strict;
69 	guint max_results;
70 
71 	/* Pagination management. The type of pagination is set as
72 	 * pagination_type, and should be set in the init() vfunc implementation
73 	 * of any class derived from GDataQuery. It defaults to
74 	 * %GDATA_QUERY_PAGINATION_INDEXED, which most subclasses will not want.
75 	 *
76 	 * The next_uri, previous_uri or next_page_token are set by
77 	 * #GDataService if a query returns a new #GDataFeed containing them. If
78 	 * the user then calls next_page() or previous_page(), use_next_page or
79 	 * use_previous_page are set as appropriate, and the next call to
80 	 * get_uri() will return a URI for the next or previous page. This might
81 	 * be next_uri, previous_uri, or a constructed URI which appends the
82 	 * next_page_token.
83 	 *
84 	 * Note that %GDATA_QUERY_PAGINATION_TOKENS does not support returning
85 	 * to the previous page.
86 	 *
87 	 * It is not invalid to have use_next_page set and to not have a
88 	 * next_uri for %GDATA_QUERY_PAGINATION_URIS; or to not have a
89 	 * next_page_token for %GDATA_QUERY_PAGINATION_TOKENS: this signifies
90 	 * that the current set of results are the last page. There are no
91 	 * further pages. Similarly for use_previous_page and a %NULL
92 	 * previous_page.
93 	 */
94 	GDataQueryPaginationType pagination_type;
95 
96 	gchar *next_uri;
97 	gchar *previous_uri;
98 	gchar *next_page_token;
99 
100 	gboolean use_next_page;
101 	gboolean use_previous_page;
102 
103 	gchar *etag;
104 };
105 
106 enum {
107 	PROP_Q = 1,
108 	PROP_CATEGORIES,
109 	PROP_AUTHOR,
110 	PROP_UPDATED_MIN,
111 	PROP_UPDATED_MAX,
112 	PROP_PUBLISHED_MIN,
113 	PROP_PUBLISHED_MAX,
114 	PROP_START_INDEX,
115 	PROP_IS_STRICT,
116 	PROP_MAX_RESULTS,
117 	PROP_ETAG
118 };
119 
G_DEFINE_TYPE(GDataQuery,gdata_query,G_TYPE_OBJECT)120 G_DEFINE_TYPE (GDataQuery, gdata_query, G_TYPE_OBJECT)
121 
122 static void
123 gdata_query_class_init (GDataQueryClass *klass)
124 {
125 	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
126 
127 	g_type_class_add_private (klass, sizeof (GDataQueryPrivate));
128 
129 	gobject_class->set_property = gdata_query_set_property;
130 	gobject_class->get_property = gdata_query_get_property;
131 	gobject_class->finalize = gdata_query_finalize;
132 
133 	klass->get_query_uri = get_query_uri;
134 
135 	/**
136 	 * GDataQuery:q:
137 	 *
138 	 * A full-text query string.
139 	 *
140 	 * When creating a query, list search terms separated by spaces, in the form <userinput>term1 term2 term3</userinput>.
141 	 * (As with all of the query parameter values, the spaces must be URL encoded.) The service returns all entries that match all of the
142 	 * search terms (like using AND between terms). Like Google's web search, a service searches on complete words (and related words with
143 	 * the same stem), not substrings.
144 	 *
145 	 * To search for an exact phrase, enclose the phrase in quotation marks: <userinput>"exact phrase"</userinput>.
146 	 *
147 	 * To exclude entries that match a given term, use the form <userinput>-term</userinput>.
148 	 *
149 	 * The search is case-insensitive.
150 	 *
151 	 * Example: to search for all entries that contain the exact phrase "Elizabeth Bennet" and the word "Darcy" but don't contain the
152 	 * word "Austen", use the following query: <userinput>"Elizabeth Bennet" Darcy -Austen</userinput>.
153 	 */
154 	g_object_class_install_property (gobject_class, PROP_Q,
155 	                                 g_param_spec_string ("q",
156 	                                                      "Query terms", "Query terms for which to search.",
157 	                                                      NULL,
158 	                                                      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
159 
160 	/**
161 	 * GDataQuery:categories:
162 	 *
163 	 * A category filter.
164 	 *
165 	 * You can query on multiple categories by listing multiple categories separated by slashes. The service returns all entries that match all
166 	 * of the categories (like using AND between terms). For example: <userinput>Fritz/Laurie</userinput> returns
167 	 * entries that match both categories ("Fritz" and "Laurie").
168 	 *
169 	 * To do an OR between terms, use a pipe character (<literal>|</literal>). For example: <userinput>Fritz\%7CLaurie</userinput> returns
170 	 * entries that match either category.
171 	 *
172 	 * 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
173 	 * specification. (Roughly, the "term" is the internal string used by the software to identify the category, while the "label" is the
174 	 * human-readable string presented to a user in a user interface.)
175 	 *
176 	 * To exclude entries that match a given category, use the form <userinput>-categoryname</userinput>.
177 	 *
178 	 * To query for a category that has a scheme – such as <literal>&lt;category scheme="urn:google.com" term="public"/&gt;</literal> – you must
179 	 * place the scheme in curly braces before the category name. For example: <userinput>{urn:google.com}public</userinput>. To match a category
180 	 * 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.
181 	 *
182 	 * The above features can be combined. For example: <userinput>A|-{urn:google.com}B/-C</userinput> means (A OR (NOT B)) AND (NOT C).
183 	 */
184 	g_object_class_install_property (gobject_class, PROP_CATEGORIES,
185 	                                 g_param_spec_string ("categories",
186 	                                                      "Category string", "Category search string.",
187 	                                                      NULL,
188 	                                                      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
189 
190 	/**
191 	 * GDataQuery:author:
192 	 *
193 	 * An entry author. The service returns entries where the author name and/or e-mail address match your query string.
194 	 */
195 	g_object_class_install_property (gobject_class, PROP_AUTHOR,
196 	                                 g_param_spec_string ("author",
197 	                                                      "Author", "Author search string.",
198 	                                                      NULL,
199 	                                                      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
200 
201 	/**
202 	 * GDataQuery:updated-min:
203 	 *
204 	 * Lower bound on the entry update date, inclusive.
205 	 */
206 	g_object_class_install_property (gobject_class, PROP_UPDATED_MIN,
207 	                                 g_param_spec_int64 ("updated-min",
208 	                                                     "Minimum update date", "Minimum date for updates on returned entries.",
209 	                                                     -1, G_MAXINT64, -1,
210 	                                                     G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
211 
212 	/**
213 	 * GDataQuery:updated-max:
214 	 *
215 	 * Upper bound on the entry update date, exclusive.
216 	 */
217 	g_object_class_install_property (gobject_class, PROP_UPDATED_MAX,
218 	                                 g_param_spec_int64 ("updated-max",
219 	                                                     "Maximum update date", "Maximum date for updates on returned entries.",
220 	                                                     -1, G_MAXINT64, -1,
221 	                                                     G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
222 
223 	/**
224 	 * GDataQuery:published-min:
225 	 *
226 	 * Lower bound on the entry publish date, inclusive.
227 	 */
228 	g_object_class_install_property (gobject_class, PROP_PUBLISHED_MIN,
229 	                                 g_param_spec_int64 ("published-min",
230 	                                                     "Minimum publish date", "Minimum date for returned entries to be published.",
231 	                                                     -1, G_MAXINT64, -1,
232 	                                                     G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
233 
234 	/**
235 	 * GDataQuery:published-max:
236 	 *
237 	 * Upper bound on the entry publish date, exclusive.
238 	 */
239 	g_object_class_install_property (gobject_class, PROP_PUBLISHED_MAX,
240 	                                 g_param_spec_int64 ("published-max",
241 	                                                     "Maximum publish date", "Maximum date for returned entries to be published.",
242 	                                                     -1, G_MAXINT64, -1,
243 	                                                     G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
244 
245 	/**
246 	 * GDataQuery:start-index:
247 	 *
248 	 * The one-based index of the first result to be retrieved. Use gdata_query_next_page() and gdata_query_previous_page() to
249 	 * implement pagination, rather than manually changing #GDataQuery:start-index.
250 	 *
251 	 * Use <code class="literal">0</code> to not specify a start index.
252 	 */
253 	g_object_class_install_property (gobject_class, PROP_START_INDEX,
254 	                                 g_param_spec_uint ("start-index",
255 	                                                    "Start index", "One-based result start index.",
256 	                                                    0, G_MAXUINT, 0,
257 	                                                    G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
258 
259 	/**
260 	 * GDataQuery:is-strict:
261 	 *
262 	 * Strict query parameter checking. If this is enabled, an error will be returned by the online service if a parameter is
263 	 * not recognised.
264 	 *
265 	 * Since: 0.2.0
266 	 */
267 	g_object_class_install_property (gobject_class, PROP_IS_STRICT,
268 	                                 g_param_spec_boolean ("is-strict",
269 	                                                       "Strict?", "Should the server be strict about the query?",
270 	                                                       FALSE,
271 	                                                       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
272 
273 	/**
274 	 * GDataQuery:max-results:
275 	 *
276 	 * Maximum number of results to be retrieved. Most services have a default #GDataQuery:max-results size imposed by the server; if you wish
277 	 * to receive the entire feed, specify a large number such as %G_MAXUINT for this property.
278 	 *
279 	 * Use <code class="literal">0</code> to not specify a maximum number of results.
280 	 */
281 	g_object_class_install_property (gobject_class, PROP_MAX_RESULTS,
282 	                                 g_param_spec_uint ("max-results",
283 	                                                    "Maximum number of results", "The maximum number of entries to return.",
284 	                                                    0, G_MAXUINT, 0,
285 	                                                    G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
286 
287 	/**
288 	 * GDataQuery:etag:
289 	 *
290 	 * 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
291 	 * returned unnecessarily.
292 	 *
293 	 * 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
294 	 * query, it must be set again using gdata_query_set_etag() after setting any other properties.
295 	 *
296 	 * Since: 0.2.0
297 	 */
298 	g_object_class_install_property (gobject_class, PROP_ETAG,
299 	                                 g_param_spec_string ("etag",
300 	                                                      "ETag", "An ETag against which to check.",
301 	                                                      NULL,
302 	                                                      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
303 }
304 
305 static void
gdata_query_init(GDataQuery * self)306 gdata_query_init (GDataQuery *self)
307 {
308 	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GDATA_TYPE_QUERY, GDataQueryPrivate);
309 	self->priv->updated_min = -1;
310 	self->priv->updated_max = -1;
311 	self->priv->published_min = -1;
312 	self->priv->published_max = -1;
313 
314 	_gdata_query_set_pagination_type (self, GDATA_QUERY_PAGINATION_INDEXED);
315 }
316 
317 static void
gdata_query_finalize(GObject * object)318 gdata_query_finalize (GObject *object)
319 {
320 	GDataQueryPrivate *priv = GDATA_QUERY (object)->priv;
321 
322 	g_free (priv->q);
323 	g_free (priv->q_internal);
324 	g_free (priv->categories);
325 	g_free (priv->author);
326 	g_free (priv->next_uri);
327 	g_free (priv->previous_uri);
328 	g_free (priv->etag);
329 	g_free (priv->next_page_token);
330 
331 	/* Chain up to the parent class */
332 	G_OBJECT_CLASS (gdata_query_parent_class)->finalize (object);
333 }
334 
335 static void
gdata_query_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)336 gdata_query_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
337 {
338 	GDataQueryPrivate *priv = GDATA_QUERY (object)->priv;
339 
340 	switch (property_id) {
341 		case PROP_Q:
342 			g_value_set_string (value, priv->q);
343 			break;
344 		case PROP_CATEGORIES:
345 			g_value_set_string (value, priv->categories);
346 			break;
347 		case PROP_AUTHOR:
348 			g_value_set_string (value, priv->author);
349 			break;
350 		case PROP_UPDATED_MIN:
351 			g_value_set_int64 (value, priv->updated_min);
352 			break;
353 		case PROP_UPDATED_MAX:
354 			g_value_set_int64 (value, priv->updated_max);
355 			break;
356 		case PROP_PUBLISHED_MIN:
357 			g_value_set_int64 (value, priv->published_min);
358 			break;
359 		case PROP_PUBLISHED_MAX:
360 			g_value_set_int64 (value, priv->published_max);
361 			break;
362 		case PROP_START_INDEX:
363 			g_value_set_uint (value, priv->start_index);
364 			break;
365 		case PROP_IS_STRICT:
366 			g_value_set_boolean (value, priv->is_strict);
367 			break;
368 		case PROP_MAX_RESULTS:
369 			g_value_set_uint (value, priv->max_results);
370 			break;
371 		case PROP_ETAG:
372 			g_value_set_string (value, priv->etag);
373 			break;
374 		default:
375 			/* We don't have any other property... */
376 			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
377 			break;
378 	}
379 }
380 
381 static void
gdata_query_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)382 gdata_query_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
383 {
384 	GDataQuery *self = GDATA_QUERY (object);
385 
386 	switch (property_id) {
387 		case PROP_Q:
388 			gdata_query_set_q (self, g_value_get_string (value));
389 			break;
390 		case PROP_CATEGORIES:
391 			gdata_query_set_categories (self, g_value_get_string (value));
392 			break;
393 		case PROP_AUTHOR:
394 			gdata_query_set_author (self, g_value_get_string (value));
395 			break;
396 		case PROP_UPDATED_MIN:
397 			gdata_query_set_updated_min (self, g_value_get_int64 (value));
398 			break;
399 		case PROP_UPDATED_MAX:
400 			gdata_query_set_updated_max (self, g_value_get_int64 (value));
401 			break;
402 		case PROP_PUBLISHED_MIN:
403 			gdata_query_set_published_min (self, g_value_get_int64 (value));
404 			break;
405 		case PROP_PUBLISHED_MAX:
406 			gdata_query_set_published_max (self, g_value_get_int64 (value));
407 			break;
408 		case PROP_START_INDEX:
409 			gdata_query_set_start_index (self, g_value_get_uint (value));
410 			break;
411 		case PROP_IS_STRICT:
412 			gdata_query_set_is_strict (self, g_value_get_boolean (value));
413 			break;
414 		case PROP_MAX_RESULTS:
415 			gdata_query_set_max_results (self, g_value_get_uint (value));
416 			break;
417 		case PROP_ETAG:
418 			gdata_query_set_etag (self, g_value_get_string (value));
419 			break;
420 		default:
421 			/* We don't have any other property... */
422 			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
423 			break;
424 	}
425 }
426 
427 static void
get_query_uri(GDataQuery * self,const gchar * feed_uri,GString * query_uri,gboolean * params_started)428 get_query_uri (GDataQuery *self, const gchar *feed_uri, GString *query_uri, gboolean *params_started)
429 {
430 	GDataQueryPrivate *priv = self->priv;
431 
432 	#define APPEND_SEP g_string_append_c (query_uri, (*params_started == FALSE) ? '?' : '&'); *params_started = TRUE;
433 
434 	/* Categories */
435 	if (priv->categories != NULL) {
436 		g_string_append (query_uri, "/-/");
437 		g_string_append_uri_escaped (query_uri, priv->categories, "/", FALSE);
438 	}
439 
440 	/* q param */
441 	if (priv->q != NULL || priv->q_internal != NULL) {
442 		APPEND_SEP
443 		g_string_append (query_uri, "q=");
444 
445 		if (priv->q != NULL) {
446 			g_string_append_uri_escaped (query_uri, priv->q, NULL, FALSE);
447 			if (priv->q_internal != NULL)
448 				g_string_append (query_uri, "%20and%20");
449 		}
450 		if (priv->q_internal != NULL)
451 			g_string_append_uri_escaped (query_uri, priv->q_internal, NULL, FALSE);
452 	}
453 
454 	if (priv->author != NULL) {
455 		APPEND_SEP
456 		g_string_append (query_uri, "author=");
457 		g_string_append_uri_escaped (query_uri, priv->author, NULL, FALSE);
458 	}
459 
460 	if (priv->updated_min != -1) {
461 		gchar *updated_min;
462 
463 		APPEND_SEP
464 		g_string_append (query_uri, "updated-min=");
465 		updated_min = gdata_parser_int64_to_iso8601 (priv->updated_min);
466 		g_string_append (query_uri, updated_min);
467 		g_free (updated_min);
468 	}
469 
470 	if (priv->updated_max != -1) {
471 		gchar *updated_max;
472 
473 		APPEND_SEP
474 		g_string_append (query_uri, "updated-max=");
475 		updated_max = gdata_parser_int64_to_iso8601 (priv->updated_max);
476 		g_string_append (query_uri, updated_max);
477 		g_free (updated_max);
478 	}
479 
480 	if (priv->published_min != -1) {
481 		gchar *published_min;
482 
483 		APPEND_SEP
484 		g_string_append (query_uri, "published-min=");
485 		published_min = gdata_parser_int64_to_iso8601 (priv->published_min);
486 		g_string_append (query_uri, published_min);
487 		g_free (published_min);
488 	}
489 
490 	if (priv->published_max != -1) {
491 		gchar *published_max;
492 
493 		APPEND_SEP
494 		g_string_append (query_uri, "published-max=");
495 		published_max = gdata_parser_int64_to_iso8601 (priv->published_max);
496 		g_string_append (query_uri, published_max);
497 		g_free (published_max);
498 	}
499 
500 	if (priv->start_index > 0) {
501 		APPEND_SEP
502 		g_string_append_printf (query_uri, "start-index=%u", priv->start_index);
503 	}
504 
505 	if (priv->is_strict == TRUE) {
506 		APPEND_SEP
507 		g_string_append (query_uri, "strict=true");
508 	}
509 
510 	if (priv->max_results > 0) {
511 		APPEND_SEP
512 		g_string_append_printf (query_uri, "max-results=%u", priv->max_results);
513 	}
514 
515 	if (priv->pagination_type == GDATA_QUERY_PAGINATION_TOKENS && priv->use_next_page &&
516 	    priv->next_page_token != NULL && *priv->next_page_token != '\0') {
517 		APPEND_SEP
518 		g_string_append (query_uri, "pageToken=");
519 		g_string_append_uri_escaped (query_uri, priv->next_page_token, NULL, FALSE);
520 	}
521 }
522 
523 /**
524  * gdata_query_new:
525  * @q: (allow-none): a query string, or %NULL
526  *
527  * Creates a new #GDataQuery with its #GDataQuery:q property set to @q.
528  *
529  * Return value: a new #GDataQuery
530  */
531 GDataQuery *
gdata_query_new(const gchar * q)532 gdata_query_new (const gchar *q)
533 {
534 	return g_object_new (GDATA_TYPE_QUERY, "q", q, NULL);
535 }
536 
537 /**
538  * gdata_query_new_with_limits:
539  * @q: (allow-none): a query string, or %NULL
540  * @start_index: a one-based start index for the results, or <code class="literal">0</code>
541  * @max_results: the maximum number of results to return, or <code class="literal">0</code>
542  *
543  * Creates a new #GDataQuery with its #GDataQuery:q property set to @q, and the limits @start_index and @max_results
544  * applied.
545  *
546  * Return value: a new #GDataQuery
547  */
548 GDataQuery *
gdata_query_new_with_limits(const gchar * q,guint start_index,guint max_results)549 gdata_query_new_with_limits (const gchar *q, guint start_index, guint max_results)
550 {
551 	return g_object_new (GDATA_TYPE_QUERY,
552 	                     "q", q,
553 	                     "start-index", start_index,
554 	                     "max-results", max_results,
555 	                     NULL);
556 }
557 
558 /**
559  * gdata_query_get_query_uri:
560  * @self: a #GDataQuery
561  * @feed_uri: the feed URI on which to build the query URI
562  *
563  * Builds a query URI from the given base feed URI, using the properties of the #GDataQuery. This function will take care
564  * of all necessary URI escaping, so it should <emphasis>not</emphasis> be done beforehand.
565  *
566  * The query URI is what functions like gdata_service_query() use to query the online service.
567  *
568  * Return value: a query URI; free with g_free()
569  */
570 gchar *
gdata_query_get_query_uri(GDataQuery * self,const gchar * feed_uri)571 gdata_query_get_query_uri (GDataQuery *self, const gchar *feed_uri)
572 {
573 	GDataQueryClass *klass;
574 	GString *query_uri;
575 	gboolean params_started;
576 
577 	g_return_val_if_fail (GDATA_IS_QUERY (self), NULL);
578 	g_return_val_if_fail (feed_uri != NULL, NULL);
579 
580 	/* Check to see if we're paginating first */
581 	if (self->priv->pagination_type == GDATA_QUERY_PAGINATION_URIS) {
582 		if (self->priv->use_next_page)
583 			return g_strdup (self->priv->next_uri);
584 		if (self->priv->use_previous_page)
585 			return g_strdup (self->priv->previous_uri);
586 	}
587 
588 	klass = GDATA_QUERY_GET_CLASS (self);
589 	g_assert (klass->get_query_uri != NULL);
590 
591 	/* Determine whether the first param has already been appended (e.g. it exists in the feed_uri) */
592 	params_started = (strstr (feed_uri, "?") != NULL) ? TRUE : FALSE;
593 
594 	/* Build the query URI */
595 	query_uri = g_string_new (feed_uri);
596 	klass->get_query_uri (self, feed_uri, query_uri, &params_started);
597 
598 	return g_string_free (query_uri, FALSE);
599 }
600 
601 /* Used internally by child classes of GDataQuery to add search clauses that represent service-specific
602  * query properties. For example, in the Drive v2 API, certain GDataDocumentsQuery properties like
603  * show-deleted and show-folders no longer have their own parameters, but have to be specified as a search
604  * clause in the query string. */
605 void
_gdata_query_add_q_internal(GDataQuery * self,const gchar * q)606 _gdata_query_add_q_internal (GDataQuery *self, const gchar *q)
607 {
608 	GDataQueryPrivate *priv = self->priv;
609 	GString *str;
610 
611 	g_return_if_fail (GDATA_IS_QUERY (self));
612 	g_return_if_fail (q != NULL && q[0] != '\0');
613 
614 	str = g_string_new (priv->q_internal);
615 
616 	/* Search parameters: https://developers.google.com/drive/web/search-parameters */
617 	if (str->len > 0)
618 		g_string_append (str, " and ");
619 
620 	g_string_append (str, q);
621 
622 	g_free (priv->q_internal);
623 	priv->q_internal = g_string_free (str, FALSE);
624 }
625 
626 /* Used internally by child classes of GDataQuery to clear the internal query string when building the
627  * query URI in GDataQueryClass->get_query_uri */
628 void
_gdata_query_clear_q_internal(GDataQuery * self)629 _gdata_query_clear_q_internal (GDataQuery *self)
630 {
631 	GDataQueryPrivate *priv = self->priv;
632 
633 	g_return_if_fail (GDATA_IS_QUERY (self));
634 
635 	g_free (priv->q_internal);
636 	priv->q_internal = NULL;
637 }
638 
639 /**
640  * gdata_query_get_q:
641  * @self: a #GDataQuery
642  *
643  * Gets the #GDataQuery:q property.
644  *
645  * Return value: the q property, or %NULL if it is unset
646  */
647 const gchar *
gdata_query_get_q(GDataQuery * self)648 gdata_query_get_q (GDataQuery *self)
649 {
650 	g_return_val_if_fail (GDATA_IS_QUERY (self), NULL);
651 	return self->priv->q;
652 }
653 
654 /**
655  * gdata_query_set_q:
656  * @self: a #GDataQuery
657  * @q: (allow-none): a new query string, or %NULL
658  *
659  * Sets the #GDataQuery:q property of the #GDataQuery to the new query string, @q.
660  *
661  * Set @q to %NULL to unset the property in the query URI.
662  */
663 void
gdata_query_set_q(GDataQuery * self,const gchar * q)664 gdata_query_set_q (GDataQuery *self, const gchar *q)
665 {
666 	g_return_if_fail (GDATA_IS_QUERY (self));
667 
668 	g_free (self->priv->q);
669 	self->priv->q = g_strdup (q);
670 	g_object_notify (G_OBJECT (self), "q");
671 
672 	/* Our current ETag will no longer be relevant */
673 	gdata_query_set_etag (self, NULL);
674 }
675 
676 /**
677  * gdata_query_get_categories:
678  * @self: a #GDataQuery
679  *
680  * Gets the #GDataQuery:categories property.
681  *
682  * Return value: the categories property, or %NULL if it is unset
683  */
684 const gchar *
gdata_query_get_categories(GDataQuery * self)685 gdata_query_get_categories (GDataQuery *self)
686 {
687 	g_return_val_if_fail (GDATA_IS_QUERY (self), NULL);
688 	return self->priv->categories;
689 }
690 
691 /**
692  * gdata_query_set_categories:
693  * @self: a #GDataQuery
694  * @categories: (allow-none): the new category string, or %NULL
695  *
696  * Sets the #GDataQuery:categories property of the #GDataQuery to the new category string, @categories.
697  *
698  * Set @categories to %NULL to unset the property in the query URI.
699  */
700 void
gdata_query_set_categories(GDataQuery * self,const gchar * categories)701 gdata_query_set_categories (GDataQuery *self, const gchar *categories)
702 {
703 	g_return_if_fail (GDATA_IS_QUERY (self));
704 
705 	g_free (self->priv->categories);
706 	self->priv->categories = g_strdup (categories);
707 	g_object_notify (G_OBJECT (self), "categories");
708 
709 	/* Our current ETag will no longer be relevant */
710 	gdata_query_set_etag (self, NULL);
711 }
712 
713 /**
714  * gdata_query_get_author:
715  * @self: a #GDataQuery
716  *
717  * Gets the #GDataQuery:author property.
718  *
719  * Return value: the author property, or %NULL if it is unset
720  */
721 const gchar *
gdata_query_get_author(GDataQuery * self)722 gdata_query_get_author (GDataQuery *self)
723 {
724 	g_return_val_if_fail (GDATA_IS_QUERY (self), NULL);
725 	return self->priv->author;
726 }
727 
728 /**
729  * gdata_query_set_author:
730  * @self: a #GDataQuery
731  * @author: (allow-none): the new author string, or %NULL
732  *
733  * Sets the #GDataQuery:author property of the #GDataQuery to the new author string, @author.
734  *
735  * Set @author to %NULL to unset the property in the query URI.
736  */
737 void
gdata_query_set_author(GDataQuery * self,const gchar * author)738 gdata_query_set_author (GDataQuery *self, const gchar *author)
739 {
740 	g_return_if_fail (GDATA_IS_QUERY (self));
741 
742 	g_free (self->priv->author);
743 	self->priv->author = g_strdup (author);
744 	g_object_notify (G_OBJECT (self), "author");
745 
746 	/* Our current ETag will no longer be relevant */
747 	gdata_query_set_etag (self, NULL);
748 }
749 
750 /**
751  * gdata_query_get_updated_min:
752  * @self: a #GDataQuery
753  *
754  * Gets the #GDataQuery:updated-min property. If the property is unset, <code class="literal">-1</code> will be returned.
755  *
756  * Return value: the updated-min property, or <code class="literal">-1</code>
757  */
758 gint64
gdata_query_get_updated_min(GDataQuery * self)759 gdata_query_get_updated_min (GDataQuery *self)
760 {
761 	g_return_val_if_fail (GDATA_IS_QUERY (self), -1);
762 	return self->priv->updated_min;
763 }
764 
765 /**
766  * gdata_query_set_updated_min:
767  * @self: a #GDataQuery
768  * @updated_min: the new minimum update time, or <code class="literal">-1</code>
769  *
770  * Sets the #GDataQuery:updated-min property of the #GDataQuery to the new minimum update time, @updated_min.
771  *
772  * Set @updated_min to <code class="literal">-1</code> to unset the property in the query URI.
773  */
774 void
gdata_query_set_updated_min(GDataQuery * self,gint64 updated_min)775 gdata_query_set_updated_min (GDataQuery *self, gint64 updated_min)
776 {
777 	g_return_if_fail (GDATA_IS_QUERY (self));
778 	g_return_if_fail (updated_min >= -1);
779 
780 	self->priv->updated_min = updated_min;
781 	g_object_notify (G_OBJECT (self), "updated-min");
782 
783 	/* Our current ETag will no longer be relevant */
784 	gdata_query_set_etag (self, NULL);
785 }
786 
787 /**
788  * gdata_query_get_updated_max:
789  * @self: a #GDataQuery
790  *
791  * Gets the #GDataQuery:updated-max property. If the property is unset, <code class="literal">-1</code> will be returned.
792  *
793  * Return value: the updated-max property, or <code class="literal">-1</code>
794  */
795 gint64
gdata_query_get_updated_max(GDataQuery * self)796 gdata_query_get_updated_max (GDataQuery *self)
797 {
798 	g_return_val_if_fail (GDATA_IS_QUERY (self), -1);
799 	return self->priv->updated_max;
800 }
801 
802 /**
803  * gdata_query_set_updated_max:
804  * @self: a #GDataQuery
805  * @updated_max: the new maximum update time, or <code class="literal">-1</code>
806  *
807  * Sets the #GDataQuery:updated-max property of the #GDataQuery to the new maximum update time, @updated_max.
808  *
809  * Set @updated_max to <code class="literal">-1</code> to unset the property in the query URI.
810  */
811 void
gdata_query_set_updated_max(GDataQuery * self,gint64 updated_max)812 gdata_query_set_updated_max (GDataQuery *self, gint64 updated_max)
813 {
814 	g_return_if_fail (GDATA_IS_QUERY (self));
815 	g_return_if_fail (updated_max >= -1);
816 
817 	self->priv->updated_max = updated_max;
818 	g_object_notify (G_OBJECT (self), "updated-max");
819 
820 	/* Our current ETag will no longer be relevant */
821 	gdata_query_set_etag (self, NULL);
822 }
823 
824 /**
825  * gdata_query_get_published_min:
826  * @self: a #GDataQuery
827  *
828  * Gets the #GDataQuery:published-min property. If the property is unset, <code class="literal">-1</code> will be returned.
829  *
830  * Return value: the published-min property, or <code class="literal">-1</code>
831  */
832 gint64
gdata_query_get_published_min(GDataQuery * self)833 gdata_query_get_published_min (GDataQuery *self)
834 {
835 	g_return_val_if_fail (GDATA_IS_QUERY (self), -1);
836 	return self->priv->published_min;
837 }
838 
839 /**
840  * gdata_query_set_published_min:
841  * @self: a #GDataQuery
842  * @published_min: the new minimum publish time, or <code class="literal">-1</code>
843  *
844  * Sets the #GDataQuery:published-min property of the #GDataQuery to the new minimum publish time, @published_min.
845  *
846  * Set @published_min to <code class="literal">-1</code> to unset the property in the query URI.
847  */
848 void
gdata_query_set_published_min(GDataQuery * self,gint64 published_min)849 gdata_query_set_published_min (GDataQuery *self, gint64 published_min)
850 {
851 	g_return_if_fail (GDATA_IS_QUERY (self));
852 	g_return_if_fail (published_min >= -1);
853 
854 	self->priv->published_min = published_min;
855 	g_object_notify (G_OBJECT (self), "published-min");
856 
857 	/* Our current ETag will no longer be relevant */
858 	gdata_query_set_etag (self, NULL);
859 }
860 
861 /**
862  * gdata_query_get_published_max:
863  * @self: a #GDataQuery
864  *
865  * Gets the #GDataQuery:published-max property. If the property is unset, <code class="literal">-1</code> will be returned.
866  *
867  * Return value: the published-max property, or <code class="literal">-1</code>
868  */
869 gint64
gdata_query_get_published_max(GDataQuery * self)870 gdata_query_get_published_max (GDataQuery *self)
871 {
872 	g_return_val_if_fail (GDATA_IS_QUERY (self), -1);
873 	return self->priv->published_max;
874 }
875 
876 /**
877  * gdata_query_set_published_max:
878  * @self: a #GDataQuery
879  * @published_max: the new maximum publish time, or <code class="literal">-1</code>
880  *
881  * Sets the #GDataQuery:published-max property of the #GDataQuery to the new maximum publish time, @published_max.
882  *
883  * Set @published_max to <code class="literal">-1</code> to unset the property in the query URI.
884  */
885 void
gdata_query_set_published_max(GDataQuery * self,gint64 published_max)886 gdata_query_set_published_max (GDataQuery *self, gint64 published_max)
887 {
888 	g_return_if_fail (GDATA_IS_QUERY (self));
889 	g_return_if_fail (published_max >= -1);
890 
891 	self->priv->published_max = published_max;
892 	g_object_notify (G_OBJECT (self), "published-max");
893 
894 	/* Our current ETag will no longer be relevant */
895 	gdata_query_set_etag (self, NULL);
896 }
897 
898 /**
899  * gdata_query_get_start_index:
900  * @self: a #GDataQuery
901  *
902  * Gets the #GDataQuery:start-index property.
903  *
904  * Return value: the start index property, or <code class="literal">0</code> if it is unset
905  */
906 guint
gdata_query_get_start_index(GDataQuery * self)907 gdata_query_get_start_index (GDataQuery *self)
908 {
909 	g_return_val_if_fail (GDATA_IS_QUERY (self), 0);
910 	return self->priv->start_index;
911 }
912 
913 /**
914  * gdata_query_set_start_index:
915  * @self: a #GDataQuery
916  * @start_index: the new start index, or <code class="literal">0</code>
917  *
918  * Sets the #GDataQuery:start-index property of the #GDataQuery to the new one-based start index, @start_index.
919  *
920  * Set @start_index to <code class="literal">0</code> to unset the property in the query URI.
921  */
922 void
gdata_query_set_start_index(GDataQuery * self,guint start_index)923 gdata_query_set_start_index (GDataQuery *self, guint start_index)
924 {
925 	g_return_if_fail (GDATA_IS_QUERY (self));
926 
927 	self->priv->start_index = start_index;
928 	g_object_notify (G_OBJECT (self), "start-index");
929 
930 	/* Our current ETag will no longer be relevant */
931 	gdata_query_set_etag (self, NULL);
932 }
933 
934 /**
935  * gdata_query_is_strict:
936  * @self: a #GDataQuery
937  *
938  * Gets the #GDataQuery:is-strict property.
939  *
940  * Return value: the strict property
941  *
942  * Since: 0.2.0
943  */
944 gboolean
gdata_query_is_strict(GDataQuery * self)945 gdata_query_is_strict (GDataQuery *self)
946 {
947 	g_return_val_if_fail (GDATA_IS_QUERY (self), FALSE);
948 	return self->priv->is_strict;
949 }
950 
951 /**
952  * gdata_query_set_is_strict:
953  * @self: a #GDataQuery
954  * @is_strict: the new strict value
955  *
956  * Sets the #GDataQuery:is-strict property of the #GDataQuery to the new strict value, @is_strict.
957  *
958  * Since: 0.2.0
959  */
960 void
gdata_query_set_is_strict(GDataQuery * self,gboolean is_strict)961 gdata_query_set_is_strict (GDataQuery *self, gboolean is_strict)
962 {
963 	g_return_if_fail (GDATA_IS_QUERY (self));
964 
965 	self->priv->is_strict = is_strict;
966 	g_object_notify (G_OBJECT (self), "is-strict");
967 
968 	/* Our current ETag will no longer be relevant */
969 	gdata_query_set_etag (self, NULL);
970 }
971 
972 /**
973  * gdata_query_get_max_results:
974  * @self: a #GDataQuery
975  *
976  * Gets the #GDataQuery:max-results property.
977  *
978  * Return value: the maximum results property, or <code class="literal">0</code> if it is unset
979  */
980 guint
gdata_query_get_max_results(GDataQuery * self)981 gdata_query_get_max_results (GDataQuery *self)
982 {
983 	g_return_val_if_fail (GDATA_IS_QUERY (self), 0);
984 	return self->priv->max_results;
985 }
986 
987 /**
988  * gdata_query_set_max_results:
989  * @self: a #GDataQuery
990  * @max_results: the new maximum results value, or <code class="literal">0</code>
991  *
992  * Sets the #GDataQuery:max-results property of the #GDataQuery to the new maximum results value, @max_results.
993  *
994  * Set @max_results to <code class="literal">0</code> to unset the property in the query URI.
995  */
996 void
gdata_query_set_max_results(GDataQuery * self,guint max_results)997 gdata_query_set_max_results (GDataQuery *self, guint max_results)
998 {
999 	g_return_if_fail (GDATA_IS_QUERY (self));
1000 
1001 	self->priv->max_results = max_results;
1002 	g_object_notify (G_OBJECT (self), "max-results");
1003 
1004 	/* Our current ETag will no longer be relevant */
1005 	gdata_query_set_etag (self, NULL);
1006 }
1007 
1008 /**
1009  * gdata_query_get_etag:
1010  * @self: a #GDataQuery
1011  *
1012  * Gets the #GDataQuery:etag property.
1013  *
1014  * Return value: the ETag property, or %NULL if it is unset
1015  *
1016  * Since: 0.2.0
1017  */
1018 const gchar *
gdata_query_get_etag(GDataQuery * self)1019 gdata_query_get_etag (GDataQuery *self)
1020 {
1021 	g_return_val_if_fail (GDATA_IS_QUERY (self), NULL);
1022 	return self->priv->etag;
1023 }
1024 
1025 /**
1026  * gdata_query_set_etag:
1027  * @self: a #GDataQuery
1028  * @etag: (allow-none): the new ETag, or %NULL
1029  *
1030  * Sets the #GDataQuery:etag property of the #GDataQuery to the new ETag, @etag.
1031  *
1032  * Set @etag to %NULL to not check against the server-side ETag.
1033  *
1034  * Since: 0.2.0
1035  */
1036 void
gdata_query_set_etag(GDataQuery * self,const gchar * etag)1037 gdata_query_set_etag (GDataQuery *self, const gchar *etag)
1038 {
1039 	g_return_if_fail (GDATA_IS_QUERY (self));
1040 
1041 	g_free (self->priv->etag);
1042 	self->priv->etag = g_strdup (etag);
1043 	g_object_notify (G_OBJECT (self), "etag");
1044 }
1045 
1046 void
_gdata_query_clear_pagination(GDataQuery * self)1047 _gdata_query_clear_pagination (GDataQuery *self)
1048 {
1049 	g_return_if_fail (GDATA_IS_QUERY (self));
1050 
1051 	switch (self->priv->pagination_type) {
1052 	case GDATA_QUERY_PAGINATION_INDEXED:
1053 		/* Nothing to do here: indexes can always be incremented. */
1054 		break;
1055 	case GDATA_QUERY_PAGINATION_URIS:
1056 		g_clear_pointer (&self->priv->next_uri, g_free);
1057 		g_clear_pointer (&self->priv->previous_uri, g_free);
1058 		break;
1059 	case GDATA_QUERY_PAGINATION_TOKENS:
1060 		g_clear_pointer (&self->priv->next_page_token, g_free);
1061 		break;
1062 	default:
1063 		g_assert_not_reached ();
1064 	}
1065 
1066 	self->priv->use_next_page = FALSE;
1067 	self->priv->use_previous_page = FALSE;
1068 }
1069 
1070 void
_gdata_query_set_pagination_type(GDataQuery * self,GDataQueryPaginationType type)1071 _gdata_query_set_pagination_type (GDataQuery               *self,
1072                                   GDataQueryPaginationType  type)
1073 {
1074 	g_debug ("%s: Pagination type set to %u", G_STRFUNC, type);
1075 
1076 	_gdata_query_clear_pagination (self);
1077 	self->priv->pagination_type = type;
1078 }
1079 
1080 void
_gdata_query_set_next_page_token(GDataQuery * self,const gchar * next_page_token)1081 _gdata_query_set_next_page_token (GDataQuery  *self,
1082                                   const gchar *next_page_token)
1083 {
1084 	g_return_if_fail (GDATA_IS_QUERY (self));
1085 	g_return_if_fail (self->priv->pagination_type ==
1086 	                  GDATA_QUERY_PAGINATION_TOKENS);
1087 
1088 	g_free (self->priv->next_page_token);
1089 	self->priv->next_page_token = g_strdup (next_page_token);
1090 }
1091 
1092 void
_gdata_query_set_next_uri(GDataQuery * self,const gchar * next_uri)1093 _gdata_query_set_next_uri (GDataQuery *self, const gchar *next_uri)
1094 {
1095 	g_return_if_fail (GDATA_IS_QUERY (self));
1096 	g_return_if_fail (self->priv->pagination_type ==
1097 	                  GDATA_QUERY_PAGINATION_URIS);
1098 
1099 	g_free (self->priv->next_uri);
1100 	self->priv->next_uri = g_strdup (next_uri);
1101 }
1102 
1103 gboolean
_gdata_query_is_finished(GDataQuery * self)1104 _gdata_query_is_finished (GDataQuery *self)
1105 {
1106 	g_return_val_if_fail (GDATA_IS_QUERY (self), FALSE);
1107 
1108 	switch (self->priv->pagination_type) {
1109 	case GDATA_QUERY_PAGINATION_INDEXED:
1110 		return FALSE;
1111 	case GDATA_QUERY_PAGINATION_URIS:
1112 		return (self->priv->next_uri == NULL && self->priv->use_next_page);
1113 	case GDATA_QUERY_PAGINATION_TOKENS:
1114 		return (self->priv->next_page_token == NULL && self->priv->use_next_page);
1115 	default:
1116 		g_assert_not_reached ();
1117 	}
1118 }
1119 
1120 void
_gdata_query_set_previous_uri(GDataQuery * self,const gchar * previous_uri)1121 _gdata_query_set_previous_uri (GDataQuery *self, const gchar *previous_uri)
1122 {
1123 	g_return_if_fail (GDATA_IS_QUERY (self));
1124 	g_return_if_fail (self->priv->pagination_type ==
1125 	                  GDATA_QUERY_PAGINATION_URIS);
1126 
1127 	g_free (self->priv->previous_uri);
1128 	self->priv->previous_uri = g_strdup (previous_uri);
1129 }
1130 
1131 /**
1132  * gdata_query_next_page:
1133  * @self: a #GDataQuery
1134  *
1135  * Changes the state of the #GDataQuery such that when gdata_query_get_query_uri() is next called, it will build the
1136  * query URI for the next page in the result set.
1137  *
1138  * Ideally, the URI of the next page is retrieved from a feed automatically when gdata_service_query() is called, but
1139  * gdata_query_next_page() will fall back to using #GDataQuery:start-index to emulate true pagination if this fails.
1140  *
1141  * You <emphasis>should not</emphasis> implement pagination manually using #GDataQuery:start-index.
1142  */
1143 void
gdata_query_next_page(GDataQuery * self)1144 gdata_query_next_page (GDataQuery *self)
1145 {
1146 	GDataQueryPrivate *priv = self->priv;
1147 
1148 	g_return_if_fail (GDATA_IS_QUERY (self));
1149 
1150 	switch (self->priv->pagination_type) {
1151 	case GDATA_QUERY_PAGINATION_INDEXED:
1152 		if (priv->start_index == 0)
1153 			priv->start_index++;
1154 		priv->start_index += priv->max_results;
1155 		break;
1156 	case GDATA_QUERY_PAGINATION_URIS:
1157 	case GDATA_QUERY_PAGINATION_TOKENS:
1158 		priv->use_next_page = TRUE;
1159 		priv->use_previous_page = FALSE;
1160 		break;
1161 	default:
1162 		g_assert_not_reached ();
1163 	}
1164 
1165 	/* Our current ETag will no longer be relevant */
1166 	gdata_query_set_etag (self, NULL);
1167 }
1168 
1169 /**
1170  * gdata_query_previous_page:
1171  * @self: a #GDataQuery
1172  *
1173  * Changes the state of the #GDataQuery such that when gdata_query_get_query_uri() is next called, it will build the
1174  * query URI for the previous page in the result set.
1175  *
1176  * See the documentation for gdata_query_next_page() for an explanation of how query URIs from the feeds are used to this end.
1177  *
1178  * Return value: %TRUE if there is a previous page and it has been switched to, %FALSE otherwise
1179  */
1180 gboolean
gdata_query_previous_page(GDataQuery * self)1181 gdata_query_previous_page (GDataQuery *self)
1182 {
1183 	GDataQueryPrivate *priv = self->priv;
1184 	gboolean retval;
1185 
1186 	g_return_val_if_fail (GDATA_IS_QUERY (self), FALSE);
1187 
1188 	switch (self->priv->pagination_type) {
1189 	case GDATA_QUERY_PAGINATION_INDEXED:
1190 		if (priv->start_index <= priv->max_results) {
1191 			retval = FALSE;
1192 		} else {
1193 			priv->start_index -= priv->max_results;
1194 			if (priv->start_index == 1)
1195 				priv->start_index--;
1196 			retval = TRUE;
1197 		}
1198 		break;
1199 	case GDATA_QUERY_PAGINATION_URIS:
1200 		if (priv->previous_uri != NULL) {
1201 			priv->use_next_page = FALSE;
1202 			priv->use_previous_page = TRUE;
1203 			retval = TRUE;
1204 		} else {
1205 			retval = FALSE;
1206 		}
1207 		break;
1208 	case GDATA_QUERY_PAGINATION_TOKENS:
1209 		/* There are no previous page tokens, unfortunately. */
1210 		retval = FALSE;
1211 		break;
1212 	default:
1213 		g_assert_not_reached ();
1214 	}
1215 
1216 	if (retval) {
1217 		/* Our current ETag will no longer be relevant */
1218 		gdata_query_set_etag (self, NULL);
1219 	}
1220 
1221 	return retval;
1222 }
1223