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><category scheme="urn:google.com" term="public"/></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, ¶ms_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